Overview
This notebook includes script for generating some of the figures and supplemental figures for the manuscript: “Inter-individual genomic and transcriptomic variation in human cortical cell types” (bioRxiv link).
Prior to running this script please perform the following steps:
1. Return to the GitHub repository where you downloaded this script (GitHub link).
2. Download the “scripts” and input_files" folders and their context to your working directory. These folders include files necessary for this script and for “SEAAD_comparison.Rmd”.
3. Download all of the subclass-level h5ad files from CELLxGENE to a “data_files” subfolder. The link to the correct CELLxGENE instance can be found at the same (GitHub link) above. When you are done, the files in “data_files” should correspond to the files listed in the “filename” columan of subclass_order.csv". If not, please update the file names in this column to match.
4. If needed set the working directory: (e.g., setwd("PATH_TO_DIRECTORY/ouput_files/")). (This should happen automatically, but sometimes it doesn’t work properly unless you manually set it).
Once the above is complete, run this script from a powerful machine (I am running RStudio on a Linux box with 1 TB of memory, and I think ~256GB is needed). Specifically, this script performs analysis associated with figures 1E, 2A-B, 2D-F, S2, S3, S4, S5, and S7, and with a few points not related to figure panels.
Prepare the data
First load the required libraries and options and set directory locations.
## Load libraries
suppressPackageStartupMessages({
library(Seurat) # For data storage and analysis
library(zellkonverter)# For reading h5ad files
library(WGCNA) # Used for generating several plots
library(SingleCellExperiment) # Required for loading data
library(fgsea) # Quick GO enrichment test
library(randomForest) # Random forest prediction
library(pheatmap) # Nice plotting function
library(extraDistr) # for multivariate hypergeometric distribution
library(ggplotify) # For converting regular plots to ggplots to save
library(ggplot2) # For plotting and saving plots
library(collapse) # Optional: For calculating variance faster (commented options for not using this) # should replace findFromGroups
library(dplyr)
library(tidyr)
})
options(stringsAsFactors=FALSE)
options(future.globals.maxSize = 32000 * 1024^2) # NOTE the large amount of memory required for running these script!
options(future.rng.onMisuse="ignore")
if(!file.exists("output_files")) dir.create("output_files")
# NOTE: REPLACE THE LINE BELOW WITH YOUR CURRENT WORKING DIRECTORY
workingFolder <- getwd()
inputFolder <- paste0(workingFolder,"input_files/")
outputFolder <- paste0(workingFolder,"output_files/")
scriptFolder <- paste0(workingFolder,"scripts/")
dataFolder <- paste0(workingFolder,"data_files/")
## Extra functions to limit library loading
source(paste0(scriptFolder,"extra_functions.r"))
setwd(outputFolder)
Next, load the all of the relevant data and metadata from the project from the h5ad files and update metadata file, and save results as subclass-level Seurat objects.
## Folder/file setup
subclass_files <- read.csv(paste0(inputFolder,"subclass_order.csv"))
subclasses <- subclass_files$subclass
files <- setNames(paste0(dataFolder,subclass_files$filename),subclasses)
## Read in subclass and cell type colors
type_info <- read.csv(paste0(inputFolder,"cluster_order_and_colors.csv"))
type_info$subclass_label <- subclass_files$subclass[match(type_info$subclass_label,subclass_files$subclass_SEAAD)]
subclass_colors <- setNames(type_info$subclass_color[match(subclasses,type_info$subclass_label)],subclasses)
## Read in the data, piping the warnings to a file
datAll <- list()
zz <- file("all.txt", open="wt") # Send warning messages to a file so avoid crashing R
sink(zz, type="message")
## Read in all of the data
for (s in subclasses){
print(s)
tmp <- readH5AD(files[s])
seu <- CreateSeuratObject(counts=assay(tmp,"UMIs"))
met <- as.data.frame(colData(tmp))
seu <- SetAssayData(seu,new.data=logCPM(seu@assays$RNA@counts),slot="data")
#seu <- SetAssayData(seu,new.data=assay(tmp,"scvi_normalized"),slot="scale.data") # Not used
seu <- AddMetaData(seu,met)
datAll[[s]] <- seu
}
[1] "Lamp5_Lhx6"
[1] "Lamp5"
[1] "Pax6"
[1] "Sncg"
[1] "Vip"
[1] "Sst Chodl"
[1] "Sst"
[1] "Pvalb"
[1] "Chandelier"
[1] "L2/3 IT"
[1] "L4 IT"
[1] "L5 IT"
[1] "L6 IT"
[1] "L6 IT Car3"
[1] "L5 ET"
[1] "L6 CT"
[1] "L6b"
[1] "L5/6 NP"
[1] "Astro"
[1] "OPC"
[1] "Oligo"
[1] "Endo"
[1] "VLMC"
[1] "Micro-PVM"
sink(type="message")
close(zz)
a=file.remove("all.txt")
Update metadata and QC filtering (keep all cells with exclude2=No + QC2 =TRUE). Also exclude donors not part of this study.
## Read in the donor metadata
donor_columns <- c("ROI","Sex","Age","Disease","Tissue.Source", "Ancestry")
donor_metadata <- read.csv(paste0(inputFolder,"human_variation_file_manifest_220624.csv"), row.names = 1)
control_donors <- c("H18.30.002", "H19.30.002", "H200.1023", "H18.30.001", "H19.30.001")
hvs_donors <- rownames(donor_metadata)[donor_metadata$use] # Note the lower-case "u" in "use". In this case, all of the rows either correspond to the patients undergoing neurosurgery that are part of this study, or the five control donors listed above.
donor_metadata <- donor_metadata[,donor_columns]
donors <- c(hvs_donors,control_donors)
## Update the metadata
for (s in subclasses){
temp_metadata <- donor_metadata[datAll[[s]]@meta.data$donor_name,]
for (col in donor_columns)
datAll[[s]] <- AddMetaData(datAll[[s]],temp_metadata[,col],col)
datAll[[s]]@meta.data$exclude2[is.na(datAll[[s]]@meta.data$exclude2)] = "No"
qc_pass <- (datAll[[s]]@meta.data$exclude2=="No")&(datAll[[s]]@meta.data$QC2_metacell_class_flag!="False")
qc_pass <- qc_pass|is.element(datAll[[s]]$donor_name,control_donors)
datAll[[s]] <- AddMetaData(datAll[[s]],qc_pass,"qc_pass")
datAll[[s]] <- datAll[[s]][,is.element(datAll[[s]]$donor_name,donors)&qc_pass]
}
At this point, all remaining cells and donors in the data set have passed QC and will be used for the remainder of the analyses.
Comparison of genes expressed and abundances
This section performs a variety of analyses related to overall gene expression levels (e.g., number of genes expressed per donor) and cell type abundances (e.g., number of cells mapped to each subclass for each donor). We start by calculating the abundance variance across donor, and scale by class. We’ll also look at the number of genes with expression in at least 25% of cells for a given cell type per donor (e.g., trimmed means > 0).
Let’s set up variables in this section.
# Get gene and cell counts per subclass
geneCounts <- cellCounts <- NULL
tmeanExpr <- list()
for (s in subclasses){
print(s)
dons <- factor(as.character(datAll[[s]]$donor_name),levels=donors)
cells <- table(dons)
cellCounts <- cbind(cellCounts,cells)
tmeanExpr[[s]] <- findFromGroups(datAll[[s]]@assays$RNA@data,dons,tmean) # collapse package has faster mean and median, but not trimmed mean
geneCounts <- cbind(geneCounts,as.numeric(colSums(tmeanExpr[[s]]>0)))
}
[1] "Lamp5_Lhx6"
[1] "Lamp5"
[1] "Pax6"
[1] "Sncg"
[1] "Vip"
[1] "Sst Chodl"
[1] "Sst"
[1] "Pvalb"
[1] "Chandelier"
[1] "L2/3 IT"
[1] "L4 IT"
[1] "L5 IT"
[1] "L6 IT"
[1] "L6 IT Car3"
[1] "L5 ET"
[1] "L6 CT"
[1] "L6b"
[1] "L5/6 NP"
[1] "Astro"
[1] "OPC"
[1] "Oligo"
[1] "Endo"
[1] "VLMC"
[1] "Micro-PVM"
colnames(geneCounts) <- colnames(cellCounts) <- subclasses
geneCounts[geneCounts==0] = NA
rownames(geneCounts) <- rownames(cellCounts)
# Set up variables for scaling to class
classes <- c("GABAergic","Glutamatergic","Non-neuronal")
cellClass <- matrix(0,nrow=dim(geneCounts)[1],ncol=3)
colnames(cellClass) <- classes
rownames(cellClass) <- rownames(geneCounts)
umiClass <- genesClass <- cellClass
classGroups <- list(subclasses[1:9],subclasses[10:18],subclasses[19:24])
names(classGroups) <- classes
# Get cell counts per subclass
cellCountsN <- cellCounts*0
for (l in donors){
for (cl in classes){
kp <- is.element(subclasses,classGroups[[cl]])
cellCountsN[l,classGroups[[cl]]] <- cellCounts[l,classGroups[[cl]]]/sum(cellCounts[l,classGroups[[cl]]])
}
}
Statistics by donor
Plot variation in abundances across donors by subclass.
library(knitr)
print(opts_current$get()$label)
NULL
vectorAbundance <- unlist(data.frame(cellCountsN[hvs_donors,]))
vectorSubclass <- NULL
for (s in subclasses) vectorSubclass <- c(vectorSubclass,rep(s,length(hvs_donors)))
vectorCols <- type_info$subclass_color[match(vectorSubclass,type_info$subclass_label)]
vectorSubclass <- factor(vectorSubclass,levels=subclasses)
plt <- as.ggplot(function(){
par(mar=c(10,5,5,5))
verboseBarplot(100*vectorAbundance+1,vectorSubclass,main="",ylab="Abundance",xlab="", ylim = c(0,100),
addScatterplot = TRUE, pch=19, pt.cex=0.5, pt.col = vectorCols, las=2, col="white")
})
plt

ggsave("abundance_barplots.pdf", plot=plt, height = 6, width = 8)
Since abundance values are inherently related (e.g., increase in OPCs means decrease in other glia by definition) it is useful to see how these proportions are correlated across donors. Let’s perform this analysis.
abundanceCor <- cor(cellCountsN[hvs_donors,])
ab = pheatmap(abundanceCor)#,cluster_cols = FALSE, cluster_rows = FALSE)
ab

ggsave("abundance_subclass_correlations.pdf", plot=ab, height = 5.5, width = 6)
Plot variation in gene counts across donors by subclass.
vectorGenes <- unlist(data.frame(geneCounts[hvs_donors,]))
plt <- as.ggplot(function(){
par(mar=c(10,5,5,5))
verboseBarplot(vectorGenes/1000,vectorSubclass,main="",ylab="Genes expressed (K)",xlab="",ylim=c(0,15),
addScatterplot = TRUE, pch=19, pt.cex=0.5, pt.col = vectorCols, las=2, col="white")
})
plt

ggsave("gene_counts_barplots.pdf", plot=plt, height = 6, width = 8)
For a few donors, the number of genes expressed in non-neuronal cells is exceptionally high, likely reflecting some issues in QCing doublets or mapping. For now, let’s not worry about this.
Statistics by disease
Let’s now look at cell type abundances and gene counts when comparing epilepsy vs. tumor.
# Exclude two donors with both conditions
kp_donors <- intersect(hvs_donors,rownames(donor_metadata)[is.element(donor_metadata$Disease,c("Epilepsy","Tumor"))])
allGroup <- factor(donor_metadata[kp_donors,"Disease"],levels = c("Epilepsy","Tumor"))
# Make plots
plt <- as.ggplot(function(){
par(mfrow=c(4,6))
par(mar=c(2,4,2,1))
for (s in subclasses)
verboseBarplot(cellCountsN[kp_donors,s],allGroup,main="",ylab=s,pt.col=subclass_colors[s],col="white",
xlab="",ylim=c(0,max(cellCountsN[kp_donors,s])*1.02),
addScatterplot = TRUE, pch=19, pt.cex=1, cex.lab=1, cex.axis = 0.7, cex.main = 1)
})
plt

ggsave("abundance_barplots_by_subclass.pdf", plot=plt, height = 8, width = 10)
The only significant difference is a decrease in PVALB+ interneurons in epilepsy cases, which makes complete sense!
Next we compare the number of genes expressed per subclass in epilepsy vs. tumor.
# Make plots
plt <- as.ggplot(function(){
par(mfrow=c(4,6))
par(mar=c(2,4,2,1))
for (s in subclasses)
verboseBarplot(geneCounts[kp_donors,s]/1000,allGroup,main="",ylab=paste(s,"(thousands)"),xlab="",
ylim = c(0,15),pt.col=subclass_colors[s],col="white",
addScatterplot = TRUE, pch=19, pt.cex=1, cex.lab=1, cex.axis = 0.7, cex.main = 1)
})
plt

ggsave("geneCount_barplots_by_subclass.pdf", plot=plt, height = 8, width = 10)
There is no difference between tumor and epilepsy at this level.
Statistics by sex
Now let’s see if there are any differences in abundance of gene expression in males vs. females.
mfGroup <- factor(donor_metadata[hvs_donors,"Sex"])
plt <- as.ggplot(function(){
par(mfrow=c(4,6))
par(mar=c(2,4,2,1))
for (s in subclasses)
verboseBarplot(cellCountsN[hvs_donors,s],mfGroup,main="",ylab=s,pt.col=subclass_colors[s],
xlab="",ylim=c(0,max(cellCountsN[hvs_donors,s])*1.02),col="white",
addScatterplot = TRUE, pch=19, pt.cex=1, cex.lab=1, cex.axis = 0.7, cex.main = 1)
})
plt
ggsave("abundance_barplots_by_sex.pdf", plot=plt, height = 8, width = 10)
plt <- as.ggplot(function(){
par(mfrow=c(4,6))
par(mar=c(2,4,2,1))
for (s in subclasses)
verboseBarplot(geneCounts[hvs_donors,s]/1000,mfGroup,main="",ylab=paste(s,"(thousands)"),xlab="",
ylim = c(0,15),pt.col=subclass_colors[s],col="white",
addScatterplot = TRUE, pch=19, pt.cex=1, cex.lab=1, cex.axis = 0.7, cex.main = 1)
})

plt

ggsave("geneCount_barplots_by_sex.pdf", plot=plt, height = 8, width = 10)
No significant differences by sex.
Statistics by Age
Now let’s see if there are any differences in abundance of gene expression by age.
ageVector <- as.numeric(donor_metadata[hvs_donors,"Age"])
plt <- as.ggplot(function(){
par(mfrow=c(4,6))
par(mar=c(2,4,2,1))
for (s in subclasses)
verboseScatterplot(ageVector,cellCountsN[hvs_donors,s],main="",ylab=s,
xlab="",ylim=c(0,max(cellCountsN[hvs_donors,s])*1.02),col=subclass_colors[s],
pch=19, cex.lab=1.1, cex.axis = 0.8, cex.main = 1.25, abline=TRUE)
})
plt
ggsave("abundance_scatterplots_by_Age.pdf", plot=plt, height = 8, width = 18)
plt <- as.ggplot(function(){
par(mfrow=c(4,6))
par(mar=c(2,4,2,1))
for (s in subclasses)
verboseScatterplot(ageVector,geneCounts[hvs_donors,s]/1000,main="",ylab=s,
xlab="",ylim=c(0,15),col=subclass_colors[s],
pch=19, cex.lab=1.1, cex.axis = 0.8, cex.main = 1.25, abline=TRUE)
})

plt

ggsave("geneCount_scatterplots_by_Age.pdf", plot=plt, height = 8, width = 18)
Statistics by ROI
Now let’s see if there are any differences in abundance of gene expression by brain region.
roiGroup <- factor(donor_metadata[hvs_donors,"ROI"])
plt <- as.ggplot(function(){
par(mfrow=c(4,6))
par(mar=c(2,4,2,1))
for (s in subclasses)
verboseBarplot(cellCountsN[hvs_donors,s],roiGroup,main="",ylab=s,
xlab="",ylim=c(0,max(cellCountsN[hvs_donors,s])*1.02),pt.col=subclass_colors[s],col="white",
addScatterplot = TRUE, pch=19, pt.cex=1, cex.lab=1, cex.axis = 0.7, cex.main = 1.5)
})
plt
ggsave("abundance_barplots_by_ROI.pdf", plot=plt, height = 8, width = 14)
plt <- as.ggplot(function(){
par(mfrow=c(4,6))
par(mar=c(2,4,2,1))
for (s in subclasses)
verboseBarplot(geneCounts[hvs_donors,s]/1000,roiGroup,main="",ylab=paste(s,"(thousands)"),xlab="",
ylim = c(0,15),pt.col=subclass_colors[s],col="white",
addScatterplot = TRUE, pch=19, pt.cex=1, cex.lab=1, cex.axis = 0.7, cex.main = 1.5)
})

plt

ggsave("geneCount_barplots_by_ROI.pdf", plot=plt, height = 8, width = 14)
There are fairly substantial changes in abundances across brain regions, most notably in Pvalb interneurons, which are more prominent in frontal than temporal cortex. Does our abundance result with respect to disease hold when only considering MTG tissue?
# Exclude two donors with both conditions
kp_donors2 <- intersect(kp_donors,rownames(donor_metadata)[donor_metadata$ROI=="MTG"])
allGroup2 <- factor(donor_metadata[kp_donors2,"Disease"],levels = c("Epilepsy","Tumor"))
# Make plots
plt <- as.ggplot(function(){
par(mfrow=c(4,6))
par(mar=c(2,4,2,1))
for (s in subclasses)
verboseBarplot(cellCountsN[kp_donors2,s],allGroup2,main="",ylab=s,pt.col=subclass_colors[s],
xlab="",ylim=c(0,max(cellCountsN[kp_donors2,s])*1.02),col="white",
addScatterplot = TRUE, pch=19, pt.cex=1, cex.lab=1, cex.axis = 0.7, cex.main = 1)
})
plt

ggsave("abundance_barplots_by_disease.pdf", plot=plt, height = 8, width = 10)
Much of the difference seems to be explained by region. Let’s explicitly test this using a linear model.
kp_donors3 <- intersect(kp_donors,intersect(kp_donors,rownames(donor_metadata)[donor_metadata$ROI!="PCx"]))
lm_pval_apply <- function(x){
dat <- data.frame(donor_metadata[kp_donors3,],Abundance = x)
lmOut <- lm(Abundance~Disease+ROI+Sex+Tissue.Source+Age, data = dat)
summary(lmOut)$coefficients[2:dim(summary(lmOut)$coefficients)[1],4]
}
cn <- c("Disease","ROI","Sex","Tissue.Source","Age")
lm_pval <- apply(cellCountsN[kp_donors3,],2,lm_pval_apply)
data.frame(t(lm_pval))
Registered S3 method overwritten by 'cli':
method from
print.boxx spatstat.geom
rownames(lm_pval) <- cn
lm_pval_adjust <- apply(lm_pval,2,p.adjust) # FDR corrected
data.frame(t(lm_pval_adjust))
After accounting for Sex and ROI, Pvalb interneurons show decreased abundance in epilepsy cases and L5 ET neurons show decreased abundance in tumor cases. Several cell types show abundance difference with respect to ROI (in principle, we could match this with the cross-areal study).
Plot these.
allGroupsDR <- paste(donor_metadata[kp_donors3,"ROI"],substr(donor_metadata[kp_donors3,"Disease"],1,2),sep=":")
# Make plots
plt <- as.ggplot(function(){
par(mfrow=c(1,4))
par(mar=c(10,3,7,1))
for (s in c("Pvalb", "L5 ET", "L4 IT", "L6b"))
verboseBarplot(cellCountsN[kp_donors3,s],allGroupsDR,main=s,ylab="",KruskalTest = FALSE,col="white",
xlab="",ylim=c(0,max(cellCountsN[kp_donors3,s])*1.1), las=2, pt.col=subclass_colors[s],
addScatterplot = TRUE, pch=19, pt.cex=1, cex.lab=1.5, cex.axis = 0.7, cex.main = 1.5)
})
plt

ggsave("select_abundance_barplots_by_disease_and_region.pdf", plot=plt, height = 4, width = 7)
For completeness, let’s try the same thing with genes expressed.
lm_pvalG <- apply(geneCounts[kp_donors3,],2,lm_pval_apply)
data.frame(t(lm_pvalG))
rownames(lm_pvalG) <- cn
lm_pval_adjustG <- apply(lm_pvalG,2,p.adjust) # FDR corrected
data.frame(t(lm_pval_adjustG))
No significant differences.
Abundances and gene counts by class
For abundances let’s look at E/I ratios, since the fraction of non-neuronal cells was set based on FACS.
EI <- NULL
for (l in hvs_donors)
EI <- c(EI,sum(cellCounts[l,classGroups[[2]]])/sum(cellCounts[l,classGroups[[1]]]))
names(EI) <- hvs_donors
dat <- data.frame(donor_metadata[kp_donors3,cn],Abundance = EI[kp_donors3])
lmOut <- lm(Abundance~Disease+ROI+Sex, data = dat)
summary(lmOut)$coefficients
Estimate Std. Error t value Pr(>|t|)
(Intercept) 2.06719235 0.2264134 9.1301692 2.555398e-13
DiseaseTumor 0.42630293 0.1773012 2.4043993 1.901332e-02
ROIMTG 0.05120958 0.2059925 0.2485992 8.044428e-01
SexM -0.31625580 0.1532633 -2.0634796 4.300369e-02
# Make plots
plt <- as.ggplot(function(){
par(mfrow=c(1,2))
par(mar=c(8,4,8,1))
verboseBarplot(EI[kp_donors3],allGroupsDR,main="",ylab="E/I ratio", xlab="",ylim=c(0,5),
addScatterplot = TRUE, pch=19, pt.cex=1, cex.lab=1, cex.axis = 1,
cex.main = 1, pt.col = "black", col="white")
set.seed(3)
plot(x=jitter(rep(0,length(EI))), EI, main="",ylab="E/I ratio",ylim=c(0,5),pch=19,
xlab="",cex.lab=1, xlim=c(-0.022,0.3))
abline(h=0)
})
plt

ggsave("EI_ratio_plots.pdf", plot=plt, height = 6, width = 8)
No difference in E/I ratio with respect to these donor metadata.
For genes expressed, we’ll define a gene as expressed in a class if it is expressed in at least one subclass
plt <- as.ggplot(function(){
par(mfrow=c(1,3))
gexL <- list()
for (cl in names(classGroups)){
subs <- classGroups[[cl]]
tme <- tmeanExpr[[subs[1]]][,donors]
tme[is.na(tme)] <- 0
for (s in subs) {
tme2 <- tmeanExpr[[s]][,donors]
tme2[is.na(tme2)] <- 0
tme <- tme+tme2
}
gex <- gexL[[cl]] <- as.numeric(colSums(tme>0))
# Make plots
par(mar=c(8,4,6,1))
kp <- match(kp_donors3,donors)
verboseBarplot(gex[kp]/1000,allGroupsDR,main="",ylab=paste(cl,"(thousands)"),xlab="",ylim=c(0,18),
addScatterplot = TRUE, pch=19, pt.cex=1, cex.lab=1, cex.axis = 1, cex.main = 1,
col="white",pt.col=type_info$class_color[grep(cl,type_info$class_label)[1]])
}
})
plt

ggsave("geneCount_by_class_barplots.pdf", plot=plt, height = 6, width = 8)
No gene differences at the class level either.
Neurosurgical vs. postmortem comparisons
In this section we’ll assess subclass variation in neurosurgical tissue and compare it to reference data… for which tissue source is it higher? How does it differ by subclass? For this analysis we limit ourselves to the 45 HVS donors with epilepsy from MTG to decrease variation associated with donor metadata and try to focus mostly on neurosurgical vs. postmortem (which is also MTG). We will use two methods for defining variation: (1) coefficient of variation (sd/mean) and (2) interquartile range / median. In both cases the idea is to define the spread of the distributions. Note that we only have three donors with cells collected from all layers using the same experimental strategy, H18.30.002, H19.30.001 and H19.30.002 , and so we will only look at these three for abundance assessments
control_donors2 <- c("H18.30.002","H19.30.001","H19.30.002")
ns_counts <- cellCountsN[hvs_donors,][(donor_metadata[hvs_donors,]$ROI=="MTG")&
(donor_metadata[hvs_donors,]$Disease=="Epilepsy"),]
mtg_ep_donors <- rownames(ns_counts)
pm_counts <- cellCountsN[control_donors2,]
ns_mean <- apply(ns_counts,2,mean,na.rm=TRUE)
pm_mean <- apply(pm_counts,2,mean,na.rm=TRUE)
ns_COV <- apply(ns_counts,2,sd,na.rm=TRUE)/ns_mean
pm_COV <- apply(pm_counts,2,sd,na.rm=TRUE)/pm_mean
ns_IQR <- apply(ns_counts,2,IQR,na.rm=TRUE)/apply(ns_counts,2,median,na.rm=TRUE)
pm_IQR <- apply(pm_counts,2,IQR,na.rm=TRUE)/apply(pm_counts,2,median,na.rm=TRUE)
Now let’s plot the results.
# NOTE: I might need to manually adjust the xlim and ylim values below
plot_tmp <- function(x,y,s,text.col="black",xlab="Post mortem controls",ylab="Neurosurgical (MTG, epilepsy)", xyline=TRUE, ...){
verboseScatterplot(x,y,col="white",xlab=xlab,ylab=ylab,...)
if(xyline) abline(0,1,col="grey",lty="dashed")
text(x,y,s,col=text.col)
}
plt <- as.ggplot(function(){
par(mfrow=c(2,2))
plot_tmp(pm_mean,ns_mean,subclasses,subclass_colors,main="mean",log="xy",xlim=c(0.0025,0.6),ylim=c(0.0025,0.6))
plot_tmp(pm_COV,ns_COV,subclasses,subclass_colors,main="COV",log="xy",xlim=c(0.05,2.5),ylim=c(0.05,2.5))
plot_tmp(pm_IQR,ns_IQR,subclasses,subclass_colors,main="IQR/median",log="xy",xlim=c(0.05,6),ylim=c(0.05,6))
plot_tmp(ns_mean,ns_COV,subclasses,subclass_colors,main="PM",xlab="Mean abundance (NS)",
ylab="COV abundance (NS)",xlim=c(-0.05,0.6),ylim=c(0,2), xyline=FALSE)
abline(v=0); abline(h=0)
})
plt

ggsave("variance_comparison_NSvsPM.pdf", plot=plt, height = 12, width = 10)
Overall, good agreement in NS vs. PM, especially for mean expression. L6 IT Car3 is an outlier on variance, likely since there are only 3 control donors. I don’t think we can really trust variance calls in our NS data in general, especially since we don’t find any correlation between NS and PM when we include the remaining two donors for neurons only. Interestingly, relative to their mean abundances, non-neurons have higher COV than neurons.
Now let’s visualize this by subclass.
countsME <- rbind(pm_counts,ns_counts)
cat <- factor(c(rep("PM",dim(pm_counts)[1]),rep("NS",dim(ns_counts)[1])),levels=c("PM","NS"))
plt <- as.ggplot(function(){
par(mfrow=c(4,6))
par(mar=c(2,5,2,0))
for (s in subclasses)
verboseBarplot(countsME[,s],cat,main="",ylab=s,xlab="",
ylim = c(0,max(countsME[,s])),col="white",pt.col = subclass_colors[s],
addScatterplot = TRUE, pch=19, pt.cex=1, cex.lab=1, cex.axis = 1, cex.main = 1)
})
plt

ggsave("abundance_barplots_by_NSvsPM.pdf", plot=plt, height = 8, width = 10)
Now let’s revisit gene expression changes. We can compare gene expression differences and see how these relate to what we published in Hodge et al 2019 (for L5 only). Let’s first plot the number of genes expressed per class in postmortem vs. neurosurgical, considering on MTG donors.
geneCountsME <- geneCounts[c(control_donors2,mtg_ep_donors),]
cat <- factor(c(rep("PM",length(control_donors2)),rep("NS",length(mtg_ep_donors))),levels=c("PM","NS"))
plt <- as.ggplot(function(){
par(mfrow=c(4,6))
par(mar=c(2,5,2,0))
for (s in subclasses)
verboseBarplot(geneCountsME[,s]/1000,cat,main="",ylab=paste(s,"(thousands)"),xlab="",
ylim = c(0,15),col="white",pt.col = subclass_colors[s],
addScatterplot = TRUE, pch=19, pt.cex=1, cex.lab=1, cex.axis = 1, cex.main = 1)
})
plt

ggsave("geneCounts_barplots_by_NSvsPM.pdf", plot=plt, height = 8, width = 10)
Some glial cell types have more genes expressed in neurosurgical than postmortem cases, although I expect this is probably an issue with QC, rather than a real result. Otherwise, there are comparable genes expressed in the reference and in neurosurgical tissue. Let’s plot this more compactly for inclusion in a supplemental figure.
df = data.frame(X=colMeans(geneCountsME[cat=="PM",],na.rm=TRUE),errX=apply(geneCountsME[cat=="PM",],2,sd,na.rm=TRUE),
Y=colMeans(geneCountsME[cat=="NS",],na.rm=TRUE),errY=apply(geneCountsME[cat=="NS",],2,sd,na.rm=TRUE))/1000
rangeX = range(c(df$X + df$errX, df$X - df$errX))
rangeY = range(c(df$Y + df$errY, df$Y - df$errY))
plt <- as.ggplot(function(){
par(mfrow=c(1,2))
plot(df$X, df$Y, xlim = rangeX, ylim = rangeY,xlab="Genes detected (thousands, postmortem)",
ylab="Genes detected (thousands, neurosurgical)",pch=19,cex=2,col=subclass_colors)
segments(df$X, df$Y - df$errY, df$X, df$Y + df$errY,col=subclass_colors)
segments(df$X - df$errX, df$Y, df$X + df$errX, df$Y,col=subclass_colors)
abline(0,1,col="grey")
verboseScatterplot(df$X, df$Y, xlim = rangeX, ylim = rangeY,xlab="Genes detected (thousands, postmortem)",
ylab="Genes detected (thousands, neurosurgical)",pch=19,cex=0.25,col=subclass_colors)
segments(df$X, df$Y - df$errY, df$X, df$Y + df$errY,col=subclass_colors)
segments(df$X - df$errX, df$Y, df$X + df$errX, df$Y,col=subclass_colors)
abline(0,1,col="grey")
text(df$X, df$Y, subclasses,col=subclass_colors)
})
plt

ggsave("geneCounts_means_scatterplots_by_NSvsPM.pdf", plot=plt, height = 5, width = 11)
Compare the SDs in neurosurgical vs. postmortem tissues.
x = c(df$errX,df$errY)
g = c(rep("PM",length(subclasses)),rep("NS",length(subclasses)))
verboseBarplot(x,g,xlab="",ylab="SD genes detected (thousands)",col="white")

Let’s look specifically at which genes are more highly expressed by source for Pvalb, L5 ET, and L2/3 IT types (the two types with abundance changes and a type with many nuclei and large variation), hopefully somewhat representative of all types.
median_expression_CT <- median_expression_NS <- list()
for (s in subclasses){
ct_tmp <- tmeanExpr[[s]][,mtg_ep_donors]
ct_tmp[ct_tmp==0] <- NA
median_expression_CT[[s]] <- apply(ct_tmp,1,median,na.rm=TRUE)
pm_tmp <- tmeanExpr[[s]][,control_donors]
pm_tmp[pm_tmp==0] <- NA
median_expression_NS[[s]] <- apply(pm_tmp,1,median,na.rm=TRUE)
}
plt <- as.ggplot(function(){
par(mfrow=c(1,4))
par(mar=c(8,4,5,1))
for (s in c("Pvalb","L5 ET","L2/3 IT","Oligo")){
kpGn <- pmax(median_expression_CT[[s]],median_expression_NS[[s]])>0
x <- median_expression_CT[[s]][kpGn]
y <- median_expression_NS[[s]][kpGn]
verboseScatterplot(x,y,pch=19,main=s,xlab="Median log2(CPM+1): PM",ylab="Median log2(CPM+1): NS",cex=0.5)
nm <- names(c(head(sort(median_expression_CT[[s]]-median_expression_NS[[s]]),5),
head(sort(median_expression_NS[[s]]-median_expression_CT[[s]]),5)))
text(x[nm],y[nm],nm,cex=0.75)
}
})
plt

ggsave("top_dex_genes_by_subclass_scatterplot_NSvsPM.pdf", plot=plt, height = 4, width = 18)
Good correlation overall, but some genes off diagonal that could be followed up at some point.
Assess cell type variability
The section focuses on quantifying cell type variation. We want to address the following questions:
* Can we determine a cell type variation score by comparing intra-donor vs. inter-donor variation within each cell type?
* Within types: How much variation? Which genes are most variable?
* Between types: Which types are most variable? Conserved vs. distinct genes?
* Association between variation and sex, disease, etc.
We will use two primary strategies to address this question.
1. Random forest prediction: For what fraction of cells can the cell type of origin be accurately predicted. This will be calculated separately for each donor, for each cell type, and using various subsets of the overall data set to reduce biases.
2. Gene variance score: For each gene, how variable is expression across random cells from the same vs. distinct donors? Which genes show the most variance after accounting for expression levels? Is this conserved across types? Which types have the most variance?
Both methods will rely on log2CPM data followed by standard Seurat processing as a baseline for analysis, which we already have. To avoid relying on too sparse data, we exclude cells from donors with fewer than 4 cells in a given type. This allows us to perform random forest predictions with four groups per cell type. In this first section we build vectors for cells to keep.
seurat_all <- datAll
for (s in names(datAll)){
kpdn <- names(table(datAll[[s]]$donor_name))[table(datAll[[s]]$donor_name)>=4]
kp <- is.element(datAll[[s]]$donor_name,intersect(hvs_donors,kpdn))
seurat_all[[s]] <- datAll[[s]][,kp]
seurat_all[[s]]$donor_name <- droplevels(seurat_all[[s]]$donor_name)
}
Let’s run random forest prediction for all subclasses using PCs as baseline (helper functions are in the sourced function file). Let’s run the random forest calculations. Note that this is fairly slow (~a couple of hours to run). The specific algorithm is as follows:
1. For each subclass, select (up to) 24 cells per donor
2. Run random forest prediction using 75% training, 25% test four times to get predictions for all 24 cells
3. Repeat first two steps 25 times to calculate mean and standard deviation of RF prediction scores per subclass, per donor
4. Use these values for downstream analysis
numberOfPermutations = 25
cellsPerPermutation = 24
numberOfPCs = 30
predictions <- list()
correct <- matrix(0,nrow=length(subclasses), ncol=numberOfPermutations)
rownames(correct) <- subclasses
print(paste(date(),"- start"))
[1] "Wed Dec 21 15:38:01 2022 - start"
for (i in 1:numberOfPermutations){
print(i)
predictions[[i]] <- list()
# Run random forest prediction
for (ct in subclasses){
tmpSamp <- subsampleCells(seurat_all[[ct]]$donor_name,cellsPerPermutation,seed=i)
predictions[[i]][[ct]] <- rf_prediction_from_seurat(seurat_all[[ct]][,tmpSamp], pcs=numberOfPCs, seed=1)
correct[ct,i] <- mean(predictions[[i]][[ct]][,2]==predictions[[i]][[ct]][,1], na.rm=TRUE)
}
}
[1] 1
[1] 2
[1] 3
[1] 4
[1] 5
[1] 6
[1] 7
[1] 8
[1] 9
[1] 10
[1] 11
[1] 12
[1] 13
[1] 14
[1] 15
[1] 16
[1] 17
[1] 18
[1] 19
[1] 20
[1] 21
[1] 22
[1] 23
[1] 24
[1] 25
print(paste(date(),"- end"))
[1] "Wed Dec 21 17:56:53 2022 - end"
Repeat these calculations using the most variable genes. THIS SECTION IS VERY SLOW!!! It will likely take a few days to run.
numberOfPermutations_var = numberOfPermutations
predictions_var <- importance_var <- list()
correct_var <- matrix(0,nrow=length(subclasses), ncol=numberOfPermutations_var)
rownames(correct_var) <- subclasses
print(paste(date(),"- start"))
[1] "Wed Dec 21 17:56:53 2022 - start"
for (i in 1:numberOfPermutations_var){
print(paste(i,"- ",date()))
predictions_var[[i]] <- importance_var[[i]] <- list()
# Run random forest prediction
for (ct in subclasses){
print(ct)
tmpSamp <- subsampleCells(seurat_all[[ct]]$donor_name,cellsPerPermutation,seed=i)
rfOut <- rf_prediction_from_logCPM(seurat_all[[ct]][,tmpSamp])
predictions_var[[i]][[ct]] <- rfOut$predictions
importance_var[[i]][[ct]] <- rfOut$importance
correct_var[ct,i] <- mean(predictions_var[[i]][[ct]][,2]==predictions_var[[i]][[ct]][,1], na.rm=TRUE)
}
save(predictions_var,importance_var,correct_var,ct,i,file="importance_genes_and_predictions.RData") # in case it crashes
}
[1] "1 - Wed Dec 21 17:56:53 2022"
[1] "Lamp5_Lhx6"
[1] "Lamp5"
[1] "Pax6"
[1] "Sncg"
[1] "Vip"
[1] "Sst Chodl"
[1] "Sst"
[1] "Pvalb"
[1] "Chandelier"
[1] "L2/3 IT"
[1] "L4 IT"
[1] "L5 IT"
[1] "L6 IT"
[1] "L6 IT Car3"
[1] "L5 ET"
[1] "L6 CT"
[1] "L6b"
[1] "L5/6 NP"
[1] "Astro"
[1] "OPC"
[1] "Oligo"
[1] "Endo"
[1] "VLMC"
[1] "Micro-PVM"
[1] "2 - Wed Dec 21 20:50:08 2022"
[1] "Lamp5_Lhx6"
[1] "Lamp5"
[1] "Pax6"
[1] "Sncg"
[1] "Vip"
[1] "Sst Chodl"
[1] "Sst"
[1] "Pvalb"
[1] "Chandelier"
[1] "L2/3 IT"
[1] "L4 IT"
[1] "L5 IT"
[1] "L6 IT"
[1] "L6 IT Car3"
[1] "L5 ET"
[1] "L6 CT"
[1] "L6b"
[1] "L5/6 NP"
[1] "Astro"
[1] "OPC"
[1] "Oligo"
[1] "Endo"
[1] "VLMC"
[1] "Micro-PVM"
[1] "3 - Wed Dec 21 23:43:15 2022"
[1] "Lamp5_Lhx6"
[1] "Lamp5"
[1] "Pax6"
[1] "Sncg"
[1] "Vip"
[1] "Sst Chodl"
[1] "Sst"
[1] "Pvalb"
[1] "Chandelier"
[1] "L2/3 IT"
[1] "L4 IT"
[1] "L5 IT"
[1] "L6 IT"
[1] "L6 IT Car3"
[1] "L5 ET"
[1] "L6 CT"
[1] "L6b"
[1] "L5/6 NP"
[1] "Astro"
[1] "OPC"
[1] "Oligo"
[1] "Endo"
[1] "VLMC"
[1] "Micro-PVM"
[1] "4 - Thu Dec 22 02:34:58 2022"
[1] "Lamp5_Lhx6"
[1] "Lamp5"
[1] "Pax6"
[1] "Sncg"
[1] "Vip"
[1] "Sst Chodl"
[1] "Sst"
[1] "Pvalb"
[1] "Chandelier"
[1] "L2/3 IT"
[1] "L4 IT"
[1] "L5 IT"
[1] "L6 IT"
[1] "L6 IT Car3"
[1] "L5 ET"
[1] "L6 CT"
[1] "L6b"
[1] "L5/6 NP"
[1] "Astro"
[1] "OPC"
[1] "Oligo"
[1] "Endo"
[1] "VLMC"
[1] "Micro-PVM"
[1] "5 - Thu Dec 22 05:28:13 2022"
[1] "Lamp5_Lhx6"
[1] "Lamp5"
[1] "Pax6"
[1] "Sncg"
[1] "Vip"
[1] "Sst Chodl"
[1] "Sst"
[1] "Pvalb"
[1] "Chandelier"
[1] "L2/3 IT"
[1] "L4 IT"
[1] "L5 IT"
[1] "L6 IT"
[1] "L6 IT Car3"
[1] "L5 ET"
[1] "L6 CT"
[1] "L6b"
[1] "L5/6 NP"
[1] "Astro"
[1] "OPC"
[1] "Oligo"
[1] "Endo"
[1] "VLMC"
[1] "Micro-PVM"
[1] "6 - Thu Dec 22 08:21:09 2022"
[1] "Lamp5_Lhx6"
[1] "Lamp5"
[1] "Pax6"
[1] "Sncg"
[1] "Vip"
[1] "Sst Chodl"
[1] "Sst"
[1] "Pvalb"
[1] "Chandelier"
[1] "L2/3 IT"
[1] "L4 IT"
[1] "L5 IT"
[1] "L6 IT"
[1] "L6 IT Car3"
[1] "L5 ET"
[1] "L6 CT"
[1] "L6b"
[1] "L5/6 NP"
[1] "Astro"
[1] "OPC"
[1] "Oligo"
[1] "Endo"
[1] "VLMC"
[1] "Micro-PVM"
[1] "7 - Thu Dec 22 11:15:16 2022"
[1] "Lamp5_Lhx6"
[1] "Lamp5"
[1] "Pax6"
[1] "Sncg"
[1] "Vip"
[1] "Sst Chodl"
[1] "Sst"
[1] "Pvalb"
[1] "Chandelier"
[1] "L2/3 IT"
[1] "L4 IT"
[1] "L5 IT"
[1] "L6 IT"
[1] "L6 IT Car3"
[1] "L5 ET"
[1] "L6 CT"
[1] "L6b"
[1] "L5/6 NP"
[1] "Astro"
[1] "OPC"
[1] "Oligo"
[1] "Endo"
[1] "VLMC"
[1] "Micro-PVM"
[1] "8 - Thu Dec 22 14:09:21 2022"
[1] "Lamp5_Lhx6"
[1] "Lamp5"
[1] "Pax6"
[1] "Sncg"
[1] "Vip"
[1] "Sst Chodl"
[1] "Sst"
[1] "Pvalb"
[1] "Chandelier"
[1] "L2/3 IT"
[1] "L4 IT"
[1] "L5 IT"
[1] "L6 IT"
[1] "L6 IT Car3"
[1] "L5 ET"
[1] "L6 CT"
[1] "L6b"
[1] "L5/6 NP"
[1] "Astro"
[1] "OPC"
[1] "Oligo"
[1] "Endo"
[1] "VLMC"
[1] "Micro-PVM"
[1] "9 - Thu Dec 22 17:02:40 2022"
[1] "Lamp5_Lhx6"
[1] "Lamp5"
[1] "Pax6"
[1] "Sncg"
[1] "Vip"
[1] "Sst Chodl"
[1] "Sst"
[1] "Pvalb"
[1] "Chandelier"
[1] "L2/3 IT"
[1] "L4 IT"
[1] "L5 IT"
[1] "L6 IT"
[1] "L6 IT Car3"
[1] "L5 ET"
[1] "L6 CT"
[1] "L6b"
[1] "L5/6 NP"
[1] "Astro"
[1] "OPC"
[1] "Oligo"
[1] "Endo"
[1] "VLMC"
[1] "Micro-PVM"
[1] "10 - Thu Dec 22 19:56:33 2022"
[1] "Lamp5_Lhx6"
[1] "Lamp5"
[1] "Pax6"
[1] "Sncg"
[1] "Vip"
[1] "Sst Chodl"
[1] "Sst"
[1] "Pvalb"
[1] "Chandelier"
[1] "L2/3 IT"
[1] "L4 IT"
[1] "L5 IT"
[1] "L6 IT"
[1] "L6 IT Car3"
[1] "L5 ET"
[1] "L6 CT"
[1] "L6b"
[1] "L5/6 NP"
[1] "Astro"
[1] "OPC"
[1] "Oligo"
[1] "Endo"
[1] "VLMC"
[1] "Micro-PVM"
[1] "11 - Thu Dec 22 22:48:55 2022"
[1] "Lamp5_Lhx6"
[1] "Lamp5"
[1] "Pax6"
[1] "Sncg"
[1] "Vip"
[1] "Sst Chodl"
[1] "Sst"
[1] "Pvalb"
[1] "Chandelier"
[1] "L2/3 IT"
[1] "L4 IT"
[1] "L5 IT"
[1] "L6 IT"
[1] "L6 IT Car3"
[1] "L5 ET"
[1] "L6 CT"
[1] "L6b"
[1] "L5/6 NP"
[1] "Astro"
[1] "OPC"
[1] "Oligo"
[1] "Endo"
[1] "VLMC"
[1] "Micro-PVM"
[1] "12 - Fri Dec 23 01:42:02 2022"
[1] "Lamp5_Lhx6"
[1] "Lamp5"
[1] "Pax6"
[1] "Sncg"
[1] "Vip"
[1] "Sst Chodl"
[1] "Sst"
[1] "Pvalb"
[1] "Chandelier"
[1] "L2/3 IT"
[1] "L4 IT"
[1] "L5 IT"
[1] "L6 IT"
[1] "L6 IT Car3"
[1] "L5 ET"
[1] "L6 CT"
[1] "L6b"
[1] "L5/6 NP"
[1] "Astro"
[1] "OPC"
[1] "Oligo"
[1] "Endo"
[1] "VLMC"
[1] "Micro-PVM"
[1] "13 - Fri Dec 23 04:35:32 2022"
[1] "Lamp5_Lhx6"
[1] "Lamp5"
[1] "Pax6"
[1] "Sncg"
[1] "Vip"
[1] "Sst Chodl"
[1] "Sst"
[1] "Pvalb"
[1] "Chandelier"
[1] "L2/3 IT"
[1] "L4 IT"
[1] "L5 IT"
[1] "L6 IT"
[1] "L6 IT Car3"
[1] "L5 ET"
[1] "L6 CT"
[1] "L6b"
[1] "L5/6 NP"
[1] "Astro"
[1] "OPC"
[1] "Oligo"
[1] "Endo"
[1] "VLMC"
[1] "Micro-PVM"
[1] "14 - Fri Dec 23 07:28:45 2022"
[1] "Lamp5_Lhx6"
[1] "Lamp5"
[1] "Pax6"
[1] "Sncg"
[1] "Vip"
[1] "Sst Chodl"
[1] "Sst"
[1] "Pvalb"
[1] "Chandelier"
[1] "L2/3 IT"
[1] "L4 IT"
[1] "L5 IT"
[1] "L6 IT"
[1] "L6 IT Car3"
[1] "L5 ET"
[1] "L6 CT"
[1] "L6b"
[1] "L5/6 NP"
[1] "Astro"
[1] "OPC"
[1] "Oligo"
[1] "Endo"
[1] "VLMC"
[1] "Micro-PVM"
[1] "15 - Fri Dec 23 10:21:21 2022"
[1] "Lamp5_Lhx6"
[1] "Lamp5"
[1] "Pax6"
[1] "Sncg"
[1] "Vip"
[1] "Sst Chodl"
[1] "Sst"
[1] "Pvalb"
[1] "Chandelier"
[1] "L2/3 IT"
[1] "L4 IT"
[1] "L5 IT"
[1] "L6 IT"
[1] "L6 IT Car3"
[1] "L5 ET"
[1] "L6 CT"
[1] "L6b"
[1] "L5/6 NP"
[1] "Astro"
[1] "OPC"
[1] "Oligo"
[1] "Endo"
[1] "VLMC"
[1] "Micro-PVM"
[1] "16 - Fri Dec 23 13:16:00 2022"
[1] "Lamp5_Lhx6"
[1] "Lamp5"
[1] "Pax6"
[1] "Sncg"
[1] "Vip"
[1] "Sst Chodl"
[1] "Sst"
[1] "Pvalb"
[1] "Chandelier"
[1] "L2/3 IT"
[1] "L4 IT"
[1] "L5 IT"
[1] "L6 IT"
[1] "L6 IT Car3"
[1] "L5 ET"
[1] "L6 CT"
[1] "L6b"
[1] "L5/6 NP"
[1] "Astro"
[1] "OPC"
[1] "Oligo"
[1] "Endo"
[1] "VLMC"
[1] "Micro-PVM"
[1] "17 - Fri Dec 23 16:09:16 2022"
[1] "Lamp5_Lhx6"
[1] "Lamp5"
[1] "Pax6"
[1] "Sncg"
[1] "Vip"
[1] "Sst Chodl"
[1] "Sst"
[1] "Pvalb"
[1] "Chandelier"
[1] "L2/3 IT"
[1] "L4 IT"
[1] "L5 IT"
[1] "L6 IT"
[1] "L6 IT Car3"
[1] "L5 ET"
[1] "L6 CT"
[1] "L6b"
[1] "L5/6 NP"
[1] "Astro"
[1] "OPC"
[1] "Oligo"
[1] "Endo"
[1] "VLMC"
[1] "Micro-PVM"
[1] "18 - Fri Dec 23 19:02:43 2022"
[1] "Lamp5_Lhx6"
[1] "Lamp5"
[1] "Pax6"
[1] "Sncg"
[1] "Vip"
[1] "Sst Chodl"
[1] "Sst"
[1] "Pvalb"
[1] "Chandelier"
[1] "L2/3 IT"
[1] "L4 IT"
[1] "L5 IT"
[1] "L6 IT"
[1] "L6 IT Car3"
[1] "L5 ET"
[1] "L6 CT"
[1] "L6b"
[1] "L5/6 NP"
[1] "Astro"
[1] "OPC"
[1] "Oligo"
[1] "Endo"
[1] "VLMC"
[1] "Micro-PVM"
[1] "19 - Fri Dec 23 21:55:48 2022"
[1] "Lamp5_Lhx6"
[1] "Lamp5"
[1] "Pax6"
[1] "Sncg"
[1] "Vip"
[1] "Sst Chodl"
[1] "Sst"
[1] "Pvalb"
[1] "Chandelier"
[1] "L2/3 IT"
[1] "L4 IT"
[1] "L5 IT"
[1] "L6 IT"
[1] "L6 IT Car3"
[1] "L5 ET"
[1] "L6 CT"
[1] "L6b"
[1] "L5/6 NP"
[1] "Astro"
[1] "OPC"
[1] "Oligo"
[1] "Endo"
[1] "VLMC"
[1] "Micro-PVM"
[1] "20 - Sat Dec 24 00:49:11 2022"
[1] "Lamp5_Lhx6"
[1] "Lamp5"
[1] "Pax6"
[1] "Sncg"
[1] "Vip"
[1] "Sst Chodl"
[1] "Sst"
[1] "Pvalb"
[1] "Chandelier"
[1] "L2/3 IT"
[1] "L4 IT"
[1] "L5 IT"
[1] "L6 IT"
[1] "L6 IT Car3"
[1] "L5 ET"
[1] "L6 CT"
[1] "L6b"
[1] "L5/6 NP"
[1] "Astro"
[1] "OPC"
[1] "Oligo"
[1] "Endo"
[1] "VLMC"
[1] "Micro-PVM"
[1] "21 - Sat Dec 24 03:42:52 2022"
[1] "Lamp5_Lhx6"
[1] "Lamp5"
[1] "Pax6"
[1] "Sncg"
[1] "Vip"
[1] "Sst Chodl"
[1] "Sst"
[1] "Pvalb"
[1] "Chandelier"
[1] "L2/3 IT"
[1] "L4 IT"
[1] "L5 IT"
[1] "L6 IT"
[1] "L6 IT Car3"
[1] "L5 ET"
[1] "L6 CT"
[1] "L6b"
[1] "L5/6 NP"
[1] "Astro"
[1] "OPC"
[1] "Oligo"
[1] "Endo"
[1] "VLMC"
[1] "Micro-PVM"
[1] "22 - Sat Dec 24 06:36:40 2022"
[1] "Lamp5_Lhx6"
[1] "Lamp5"
[1] "Pax6"
[1] "Sncg"
[1] "Vip"
[1] "Sst Chodl"
[1] "Sst"
[1] "Pvalb"
[1] "Chandelier"
[1] "L2/3 IT"
[1] "L4 IT"
[1] "L5 IT"
[1] "L6 IT"
[1] "L6 IT Car3"
[1] "L5 ET"
[1] "L6 CT"
[1] "L6b"
[1] "L5/6 NP"
[1] "Astro"
[1] "OPC"
[1] "Oligo"
[1] "Endo"
[1] "VLMC"
[1] "Micro-PVM"
[1] "23 - Sat Dec 24 09:30:36 2022"
[1] "Lamp5_Lhx6"
[1] "Lamp5"
[1] "Pax6"
[1] "Sncg"
[1] "Vip"
[1] "Sst Chodl"
[1] "Sst"
[1] "Pvalb"
[1] "Chandelier"
[1] "L2/3 IT"
[1] "L4 IT"
[1] "L5 IT"
[1] "L6 IT"
[1] "L6 IT Car3"
[1] "L5 ET"
[1] "L6 CT"
[1] "L6b"
[1] "L5/6 NP"
[1] "Astro"
[1] "OPC"
[1] "Oligo"
[1] "Endo"
[1] "VLMC"
[1] "Micro-PVM"
[1] "24 - Sat Dec 24 12:23:55 2022"
[1] "Lamp5_Lhx6"
[1] "Lamp5"
[1] "Pax6"
[1] "Sncg"
[1] "Vip"
[1] "Sst Chodl"
[1] "Sst"
[1] "Pvalb"
[1] "Chandelier"
[1] "L2/3 IT"
[1] "L4 IT"
[1] "L5 IT"
[1] "L6 IT"
[1] "L6 IT Car3"
[1] "L5 ET"
[1] "L6 CT"
[1] "L6b"
[1] "L5/6 NP"
[1] "Astro"
[1] "OPC"
[1] "Oligo"
[1] "Endo"
[1] "VLMC"
[1] "Micro-PVM"
[1] "25 - Sat Dec 24 15:17:04 2022"
[1] "Lamp5_Lhx6"
[1] "Lamp5"
[1] "Pax6"
[1] "Sncg"
[1] "Vip"
[1] "Sst Chodl"
[1] "Sst"
[1] "Pvalb"
[1] "Chandelier"
[1] "L2/3 IT"
[1] "L4 IT"
[1] "L5 IT"
[1] "L6 IT"
[1] "L6 IT Car3"
[1] "L5 ET"
[1] "L6 CT"
[1] "L6b"
[1] "L5/6 NP"
[1] "Astro"
[1] "OPC"
[1] "Oligo"
[1] "Endo"
[1] "VLMC"
[1] "Micro-PVM"
print(paste(date(),"- end"))
[1] "Sat Dec 24 18:11:17 2022 - end"
How do the values look for each subclass overall?
numberOfCells <- rowSums(correct)*0
for (ct in rownames(correct)) numberOfCells[ct] <- dim(seurat_all[[ct]])[2]
plt <- as.ggplot(function(){
par(mfrow=c(1,2))
verboseScatterplot(rowMeans(correct),numberOfCells,xlab="Mean RF prediction",
ylab="Number of total cells",main="",col="white",pch=19,xlim=c(0.25,0.7))
text(rowMeans(correct),numberOfCells,rownames(correct),cex=0.65,col=subclass_colors[rownames(correct)])
verboseScatterplot(rowMeans(correct),apply(correct,1,sd),xlab="Mean RF prediction",
ylab="SD of RF prediction",main="",col=subclass_colors[rownames(correct)],
pch=19,xlim=c(0.25,0.7))
cells_per_type <- paste0(round(numberOfCells/100)/10,"K")
text(rowMeans(correct),apply(correct,1,sd),paste0(rownames(correct)," (",cells_per_type,")"),cex=0.65)
})
plt
ggsave("RF_prediction_sanityCheck_scatterplots.pdf", plot=plt, height = 6, width = 12)
lev = rownames(correct)[length(rownames(correct)):1] #[order(rowMeans(correct))] # Same order as tree, but needs to be backwards to work with code.
classes = rep("Non-neuronal",length(lev))
classes[is.element(lev,subclasses[1:9])] = "GABAergic"
classes[is.element(lev,subclasses[10:18])] = "Glutamatergic"
#cols <- labels2colors(classes)
cols <- c("#FF9289","#CDA1FF","#CABD00")[c(1,1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,3,3,3,3,3,3)][24:1] # Match colors below
names(classes) <- names(cols) <- lev
xname = factor(rep(rownames(correct), ncol(correct)),levels=lev)
plt <- as.ggplot(function(){
## Let's also make a barplot
par(mfrow=c(1,1))
par(mar=c(10,8,5,3))
verboseBarplot(unlist(data.frame(correct)),xname,las=2,xlab="",ylab="",main="Fraction correctly predicted",col=cols,ylim=c(0,1))
abline(h=(0:5)/5,col="grey",lty="dotted")
})

plt

ggsave("RF_prediction_barplot_large.pdf", plot=plt, height = 6, width = 12)
Based on this metric, all of the glutamatergic types and non-neuronal cells have a higher predictive power (and therefore more donor variability) than GABAergic types and non-neural types (minus microglia). This appears relatively independent of number of total cells per group and matches expectations (although interestingly L2/3 is lower than some of the other types). In addition the standard deviation of the RF prediction is quite low. Finally, all cell types have some donor signatures. It may be worth testing this after accounting for supertypes, but overall I’m happy with this result.
Repeat for the variable genes (which is what was used for the main figure).
How do the values look for each subclass overall?
numberOfCells <- rowSums(correct_var)*0
for (ct in rownames(correct_var)) numberOfCells[ct] <- dim(seurat_all[[ct]])[2]
plt <- as.ggplot(function(){
par(mfrow=c(1,2))
verboseScatterplot(rowMeans(correct_var),numberOfCells,xlab="Mean RF prediction",
ylab="Number of total cells",main="",col="white",pch=19,xlim=c(0.3,0.8))
text(rowMeans(correct_var),numberOfCells,rownames(correct_var),cex=0.65,col=subclass_colors[rownames(correct)])
verboseScatterplot(rowMeans(correct_var),apply(correct_var,1,sd),xlab="Mean RF prediction",
ylab="SD of RF prediction",main="",col=subclass_colors[rownames(correct_var)],
pch=19,xlim=c(0.3,0.8))
cells_per_type <- paste0(round(numberOfCells/100)/10,"K")
text(rowMeans(correct_var),apply(correct_var,1,sd),paste0(rownames(correct_var)," (",cells_per_type,")"),cex=0.65)
})
plt
ggsave("RF_prediction_VARGENES_sanityCheck_scatterplots.pdf", plot=plt, height = 6, width = 12)
plt <- as.ggplot(function(){
par(mfrow=c(1,2))
verboseScatterplot(rowMeans(correct),rowMeans(correct_var),xlab="Mean RF prediction (PCs)",
ylab="Mean RF prediction (Var. Genes)",main="",col="black",cex=0.3,
pch=19,xlim=c(0.2,0.8),ylim=c(0.2,0.8))
abline(0,1,col="grey",lty="dashed")
text(rowMeans(correct),rowMeans(correct_var),rownames(correct),cex=0.65,col=subclass_colors[rownames(correct_var)])
})

plt
ggsave("RF_prediction_PCs_vs_VARGENES.pdf", plot=plt, height = 6, width = 12)
lev = rownames(correct_var)[length(rownames(correct_var)):1] #[order(rowMeans(correct_var))] # Same order as tree, but needs to be backwards to work with code.
classes = rep("Non-neuronal",length(lev))
classes[is.element(lev,subclasses[1:9])] = "GABAergic"
classes[is.element(lev,subclasses[10:18])] = "Glutamatergic"
#cols <- labels2colors(classes)
cols <- c("#FF9289","#CDA1FF","#CABD00")[c(1,1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,3,3,3,3,3,3)][24:1] # Match colors below
names(classes) <- names(cols) <- lev
xname = factor(rep(rownames(correct_var), ncol(correct_var)),levels=lev)
plt <- as.ggplot(function(){
## Let's also make a barplot
par(mfrow=c(1,1))
par(mar=c(10,8,5,3))
verboseBarplot(unlist(data.frame(correct_var)),xname,las=2,xlab="",ylab="",main="Fraction correctly predicted",col=cols,ylim=c(0,1))
abline(h=(0:5)/5,col="grey",lty="dotted")
})

plt

ggsave("RF_prediction_VARGENES_barplot_large.pdf", plot=plt, height = 6, width = 12)
Let’s retry on a per-donor level to see if the same donors are outliers for each cell type.
donors <- unique(predictions[[1]][["L2/3 IT"]][,"donor"])
dons <- sort(unique(donors))
agreements <- matrix(0,nrow = length(subclasses),ncol=length(dons))
rownames(agreements) <- subclasses
colnames(agreements) <- dons
for (ct in subclasses) for (dn in dons){
tmp <- NULL
for (num in 1:numberOfPermutations)
tmp <- c(tmp, sum((predictions_var[[num]][[ct]][,1]==dn)&(predictions_var[[num]][[ct]][,2]==dn),
na.rm=TRUE) / sum(predictions_var[[num]][[ct]][,1]==dn,na.rm=TRUE))
agreements[ct,dn] = mean(tmp,rm.na=TRUE)
}
agreements[is.nan(agreements)] = NA
#Create heatmap using pheatmap
agreements <- agreements[lev[length(lev):1],] # Reorder to match above
col_meta = data.frame(donor_metadata[dons,c("Sex","Disease","ROI")])
pheat <- pheatmap(agreements, show_rownames=TRUE, cluster_cols=TRUE, cluster_rows=FALSE, # Rows should match above
scale="none", annotation_col = col_meta, annotation_row = data.frame(celltype=classes[rownames(agreements)]),
clustering_distance_rows="euclidean", cex=1, clustering_distance_cols="euclidean",
clustering_method="complete", border_color=FALSE, main="Fraction of cells correctly mapped to donor")
pheat

ggsave("RF_prediction_main_pheatmap_plot.pdf", plot=pheat, height = 6, width = 9)
outlierFactor <- colMeans(agreements,na.rm=TRUE)
write.csv(agreements,"correct_RF_mapping_fraction.csv")
Plot the median value again a bit smaller for plotting purposes.
subclass_order <- lev[length(lev):1]
plt <- as.ggplot(function(){
par(mfrow=c(1,1))
par(mar=c(10,8,3,3))
ord <- subclass_order[length(subclass_order):1]
xname <- factor(as.character(xname),levels=ord)
verboseBarplot(unlist(data.frame(correct_var)),xname,las=2,xlab="",ylab="Fraction correctly predicted",
col=cols[ord],ylim=c(0,1), cex=0.5, cex.axis = 1, cex.lab = 1)
abline(h=(0:5)/5,col="grey",lty="dotted")
abline(h=median(unlist(data.frame(correct_var))),col="black",lwd=2)
})
plt

ggsave("RF_prediction_barplot_large.pdf", plot=plt, height = 4.5, width = 6)
There is a clear cell type separation by class, as discussed above (row colors), but the donor separation between donor demographics is less obvious. Let’s quantify this significance of this using a linear model.
lm_pval_apply(colMeans(agreements,na.rm=TRUE)[kp_donors3]) # Function is in the extra_functions.r script
DiseaseTumor ROIMTG SexM Tissue.SourceUW (Ojemann) Age
8.420071e-01 6.078504e-01 8.916606e-05 4.798269e-01 7.301628e-01
Plot a donor bar plot as well.
subclass_order <- lev[length(lev):1]
plt <- as.ggplot(function(){
par(mfrow=c(1,1))
par(mar=c(10,8,3,3))
val <- -apply(agreements,2,median,na.rm=TRUE)[pheat$tree_col$order]
names(val) <- NULL
barplot(val,ylim=c(-1,0),border = FALSE, cex.axis = 0.5, cex.lab = 0.5, las=2,
ylab="RF accuracy (median)", xlab="",main="")
abline(h=-(0:5)/5,col="grey",lty="dotted")
abline(h=-median(unlist(data.frame(correct_var))),col="black",lwd=2)
})
plt

ggsave("RF_prediction_per_donor_barplot.pdf", plot=plt, height = 1.5, width = 8)
Significant separation by sex based on linear model. Let’s plot these values and calculate Kruskal-Wallace test p-values as well.
allGroupsD <- donor_metadata[kp_donors3,"Disease"]
allGroupsR <- donor_metadata[hvs_donors,"ROI"]
allGroupsS <- donor_metadata[hvs_donors,"Sex"]
color_palatte <- setNames(c("#FF86FF","#3DC1FF","#FF86FF","#00D5FA","#7FCD00","#F9A80F","#00D979","#FF9289","#CDA1FF","#CABD00"),
c("Epilepsy","Tumor","FRO","MTG","PCx","F","M","GABAergic","Glutamatergic","Non-neuronal"))
plt <- as.ggplot(function(){
par(mfrow=c(1,4))
par(mar=c(12,9,4,1))
verboseBarplot(colMeans(agreements[,kp_donors3],na.rm=TRUE),allGroupsD,ylab="RF prediction",main="",
xlab="",ylim=c(0,1), las=2, col=color_palatte[c("Epilepsy","Tumor")],pt.col="black",KruskalTest = TRUE,
addScatterplot = TRUE, pch=19, pt.cex=1.5, cex.lab=1.5, cex.axis = 1, cex.main = 1.5)
par(mar=c(12,7,4,1))
verboseBarplot(colMeans(agreements[,hvs_donors],na.rm=TRUE),allGroupsR,ylab="RF prediction",main="",
xlab="",ylim=c(0,1), las=2, col=color_palatte[c("FRO","MTG","PCx")],pt.col="black",KruskalTest = TRUE,
addScatterplot = TRUE, pch=19, pt.cex=1.5, cex.lab=1.5, cex.axis = 1, cex.main = 1.5)
par(mar=c(12,9,4,1))
verboseBarplot(colMeans(agreements[,hvs_donors],na.rm=TRUE),allGroupsS,ylab="RF prediction",main="",
xlab="",ylim=c(0,1), las=2, col=color_palatte[c("F","M")],pt.col="black",KruskalTest = TRUE,
addScatterplot = TRUE, pch=19, pt.cex=1.5, cex.lab=1.5, cex.axis = 1, cex.main = 1.5)
par(mar=c(12,7,4,1))
verboseBarplot(rowMeans(correct[subclasses,],na.rm=TRUE),classes[subclasses],ylab="RF prediction",
xlab="",ylim=c(0,1), las=2, col=color_palatte[unique(classes[subclasses])],pt.col="black",KruskalTest = TRUE,
main="",addScatterplot = TRUE, pch=19, pt.cex=1.5, cex.lab=1.5, cex.axis = 1, cex.main = 1.5)
})
plt

ggsave("RFprediction_barplots_averaged.pdf", plot=plt, height = 5, width = 14)
Now let’s see how these RF predictions differ across donor metadata by subclass as we did with abundances above.
#cn <- c("Disease","ROI","Sex")
cn <- c("Disease","ROI","Sex","Tissue.Source","Age")
RFagreement <- t(agreements[subclasses,kp_donors3])
lm_pvalRF <- apply(RFagreement,2,lm_pval_apply) # This function is in the extra_functions.r file
rownames(lm_pvalRF) <- cn
data.frame(t(lm_pvalRF))
lm_pval_adjustRF <- apply(lm_pvalRF,2,p.adjust) # FDR corrected
data.frame(t(lm_pval_adjustRF))
plt <- as.ggplot(function(){
par(mfrow=c(4,6))
par(mar=c(2,5.5,2,1))
for (s in subclasses)
verboseBarplot(agreements[s,kp_donors3],allGroupsD,ylab=s,main="",KruskalTest = TRUE, xlab="",
ylim=c(0,1), las=2, pt.col=subclass_colors[s],col=color_palatte[c("Epilepsy","Tumor")],
addScatterplot = TRUE, pch=19, pt.cex=1, cex.lab=1.5, cex.axis = 0.7, cex.main = 1.5)
})
plt
ggsave("RFprediction_barplots_by_subclass_disease.pdf", plot=plt, height = 10, width = 14)
plt <- as.ggplot(function(){
par(mfrow=c(4,6))
par(mar=c(2,4.7,2,1))
for (s in subclasses)
verboseBarplot(agreements[s,hvs_donors],allGroupsR,ylab=s,main="",KruskalTest = TRUE, xlab="",
ylim=c(0,1), las=2, pt.col=subclass_colors[s],col=color_palatte[c("FRO","MTG","PCx")],
addScatterplot = TRUE, pch=19, pt.cex=1, cex.lab=1.5, cex.axis = 0.7, cex.main = 1.5)
})

plt
ggsave("RFprediction_barplots_by_subclass_region.pdf", plot=plt, height = 10, width = 14)
plt <- as.ggplot(function(){
par(mfrow=c(4,6))
par(mar=c(2,5.5,2,1))
for (s in subclasses)
verboseBarplot(agreements[s,hvs_donors],allGroupsS,ylab=s,main="",KruskalTest = TRUE, xlab="",
ylim=c(0,1), las=2, pt.col=subclass_colors[s],col=color_palatte[c("F","M")],
addScatterplot = TRUE, pch=19, pt.cex=1, cex.lab=1.5, cex.axis = 0.7, cex.main = 1.5)
})

plt

ggsave("RFprediction_barplots_by_subclass_sex.pdf", plot=plt, height = 10, width = 14)
See which genes are most informative in the various RF models.
gene_importances <- NULL
for (ct in subclasses){
print(ct)
iv <- NULL
for (i in 1:numberOfPermutations) for (j in 1:4){
l <- dim(importance_var[[i]][[ct]][[j]])[2]
tmp <- data.frame(gene=row.names(importance_var[[i]][[ct]][[j]]),importance_var[[i]][[ct]][[j]][,(l-1):l])
iv <- rbind(iv,tmp)
}
iv_mean <- iv %>% group_by(gene) %>% summarise(mean=mean(MeanDecreaseAccuracy))
iv_mean <- as.data.frame(iv_mean[order(-iv_mean$mean),])
gene_importances <- rbind(gene_importances,data.frame(subclass=ct,iv_mean))
}
[1] "Lamp5_Lhx6"
[1] "Lamp5"
[1] "Pax6"
[1] "Sncg"
[1] "Vip"
[1] "Sst Chodl"
[1] "Sst"
[1] "Pvalb"
[1] "Chandelier"
[1] "L2/3 IT"
[1] "L4 IT"
[1] "L5 IT"
[1] "L6 IT"
[1] "L6 IT Car3"
[1] "L5 ET"
[1] "L6 CT"
[1] "L6b"
[1] "L5/6 NP"
[1] "Astro"
[1] "OPC"
[1] "Oligo"
[1] "Endo"
[1] "VLMC"
[1] "Micro-PVM"
# Summarize by subclass
gene_importances_mean <- spread(gene_importances, subclass, mean)
rownames(gene_importances_mean) <- gene_importances_mean$gene
gene_importances_mean <- as.matrix(gene_importances_mean[,2:dim(gene_importances_mean)[2]])
head(sort(-rowMeans(gene_importances_mean,na.rm=TRUE)),50)
XIST TTTY14 UTY NLGN4Y USP9Y LRRC37A ARL17B LOC284581 TTTY15 NFAT5
-0.0071253013 -0.0045912055 -0.0044093621 -0.0043963212 -0.0043031778 -0.0039366998 -0.0029704873 -0.0021610190 -0.0015484958 -0.0014100629
SIK3 FAM157C KDM5D LOC105376839 RNR2 PCDH11Y RAPGEF6 LOC100996717 SPINK8 TXLNGY
-0.0012576959 -0.0012524710 -0.0012310169 -0.0012215661 -0.0011546760 -0.0011081040 -0.0010623643 -0.0010612703 -0.0010237585 -0.0010104677
DENND5A CRY1 KCNIP4 ZDHHC11B COX3 CPEB4 RBFOX1 COX2 ARID4B JUND
-0.0009857167 -0.0009850273 -0.0009645502 -0.0008704456 -0.0008537281 -0.0008474493 -0.0008104007 -0.0008061681 -0.0008011208 -0.0007693790
DDX3Y LINC-PINT SYT1 NFKB1 PRKY ZDHHC11 RN7SK C5orf17 CNTNAP2 HERPUD1
-0.0007530298 -0.0007448963 -0.0007230376 -0.0007203066 -0.0007014064 -0.0006674256 -0.0006630270 -0.0006484074 -0.0006442401 -0.0006234355
MAPRE2 PTPRD ANKRD10 ZBTB16 MEG3 FOSB H3F3B DDX47 NRG3 NRXN1
-0.0006149419 -0.0006025697 -0.0006021506 -0.0005807687 -0.0005804513 -0.0005802212 -0.0005674459 -0.0005670050 -0.0005602879 -0.0005594036
Split by sex.
male <- hvs_donors[donor_metadata[hvs_donors,"Sex"]=="M"]
female <- hvs_donors[donor_metadata[hvs_donors,"Sex"]=="F"]
male_importances <- female_importances <- NULL
for (ct in subclasses){
print(ct)
iv <- NULL
for (i in 1:numberOfPermutations) for (j in 1:4){
x <- importance_var[[i]][[ct]][[j]]
m <- intersect(male,colnames(x))
f <- intersect(female,colnames(x))
tmp <- data.frame(gene=row.names(x), mval = rowMeans(x[,m]), fval = rowMeans(x[,f]))
iv <- rbind(iv,tmp)
}
iv_m <- iv %>% group_by(gene) %>% summarise(mean=mean(mval))
iv_m <- as.data.frame(iv_m[order(-iv_m$mean),])
iv_f <- iv %>% group_by(gene) %>% summarise(mean=mean(fval))
iv_f <- as.data.frame(iv_f[order(-iv_f$mean),])
male_importances <- rbind(male_importances,data.frame(subclass=ct,iv_m))
female_importances <- rbind(female_importances,data.frame(subclass=ct,iv_f))
}
[1] "Lamp5_Lhx6"
[1] "Lamp5"
[1] "Pax6"
[1] "Sncg"
[1] "Vip"
[1] "Sst Chodl"
[1] "Sst"
[1] "Pvalb"
[1] "Chandelier"
[1] "L2/3 IT"
[1] "L4 IT"
[1] "L5 IT"
[1] "L6 IT"
[1] "L6 IT Car3"
[1] "L5 ET"
[1] "L6 CT"
[1] "L6b"
[1] "L5/6 NP"
[1] "Astro"
[1] "OPC"
[1] "Oligo"
[1] "Endo"
[1] "VLMC"
[1] "Micro-PVM"
# Summarize by subclass
male_importances_mean <- spread(male_importances, subclass, mean)
rownames(male_importances_mean) <- male_importances_mean$gene
male_importances_mean <- as.matrix(male_importances_mean[,2:dim(male_importances_mean)[2]])
female_importances_mean <- spread(female_importances, subclass, mean)
rownames(female_importances_mean) <- female_importances_mean$gene
female_importances_mean <- as.matrix(female_importances_mean[,2:dim(female_importances_mean)[2]])
Some plots using this information will show up later in the script.
High variance genes (Fig 1)
This section is aimed at identifying the genes with strong donor signatures. More specifically, we seek to ask which genes have variance that is highly donor-dependent (e.g., random cell pairs from different donors are more variable than pairs of cells from the same donor), rather than strictly defining variance of a given gene. This analysis is intended mostly as a lead-in to running variance partitioning analysis (see other scripts): here we find genes that are variable for any reason and there we see what fraction of a gene’s variance relates to different metadata variables.
Start by calculating expressed genes.
# Find cell type genes
kpGn <- list()
for (ct in subclasses){
tmpSamp <- subsampleCells(seurat_all[[ct]]$donor_name,cellsPerPermutation) # Speed up quantile calculation and make it fairer by subsampling by donor
kpGn[[ct]] <- apply(seurat_all[[ct]]@assays$RNA@data[,tmpSamp],1,quantile,probs=0.75)>0 # Include genes expressed in >25% of cells
}
Now calculate the gene diffs and variances.
# Variable set up
cellsPerSubclass <- 7500 #2500 # Average of 100 cell pairs per donor
gene_variances <- gene_variances_permuted <- gene_diffs <- gene_diffs_permuted <- list()
# Calculate differences between pairs of cells and associate variation of these differences
print(paste(date(),"- start"))
[1] "Sat Dec 24 18:19:32 2022 - start"
for (ct in subclasses){
tb <- table(seurat_all[[ct]]$donor_name)
tmpDonors <- names(tb)[tb>=4]
tmpSamp <- is.element(seurat_all[[ct]]$donor_name,tmpDonors)
set.seed(which(subclasses==ct))
donor1 <- sample(tmpDonors,cellsPerSubclass*3,replace=TRUE)
set.seed(which(subclasses==ct)+1)
donor2 <- sample(tmpDonors,cellsPerSubclass*3,replace=TRUE)
kpTmp <- donor1!=donor2
donor1 <- donor1[kpTmp][1:cellsPerSubclass]
donor2 <- donor2[kpTmp][1:cellsPerSubclass]
cell1a <- cell1b <- cell2a <- NULL
for (z in 1:cellsPerSubclass){
c1a <- sample(which(seurat_all[[ct]]$donor_name==donor1[z]),1)
cell1a <- c(cell1a,c1a)
cell1b <- c(cell1b,sample(which((seurat_all[[ct]]$donor_name==donor1[z])&((1:length(seurat_all[[ct]]$donor_name))!=c1a)),1))
cell2a <- c(cell2a,sample(which(seurat_all[[ct]]$donor_name==donor2[z]),1))
}
# Remove duplicates
firstOne <- match(unique(cell1a*(cell1b/7)^2),cell1a*(cell1b/7)^2)
countFirst <- length(firstOne)
datTmp <- seurat_all[[ct]]@assays$RNA@data[kpGn[[ct]],c(cell1a[firstOne],cell1b[firstOne],cell2a[firstOne])]
gene_diffs[[ct]] <- abs(datTmp[,(1:countFirst)]-datTmp[,(countFirst+1):(2*countFirst)])
gene_diffs_permuted[[ct]] <- abs(datTmp[,(1:countFirst)]-datTmp[,(2*countFirst+1):(3*countFirst)])
gene_variances[[ct]] <- t(fvar(t(as.matrix(gene_diffs[[ct]])),rep("All",countFirst)))
gene_variances_permuted[[ct]] <- t(fvar(t(as.matrix(gene_diffs_permuted[[ct]])),rep("All",countFirst)))
print(paste("Cell count:",ct,"=",dim(gene_diffs[[ct]])[2]))
}
[1] "Cell count: Lamp5_Lhx6 = 7093"
[1] "Cell count: Lamp5 = 7285"
[1] "Cell count: Pax6 = 5447"
[1] "Cell count: Sncg = 6913"
[1] "Cell count: Vip = 7494"
[1] "Cell count: Sst Chodl = 1225"
[1] "Cell count: Sst = 7398"
[1] "Cell count: Pvalb = 7485"
[1] "Cell count: Chandelier = 6084"
[1] "Cell count: L2/3 IT = 7500"
[1] "Cell count: L4 IT = 7497"
[1] "Cell count: L5 IT = 7493"
[1] "Cell count: L6 IT = 7321"
[1] "Cell count: L6 IT Car3 = 7039"
[1] "Cell count: L5 ET = 2577"
[1] "Cell count: L6 CT = 7105"
[1] "Cell count: L6b = 7138"
[1] "Cell count: L5/6 NP = 7315"
[1] "Cell count: Astro = 7418"
[1] "Cell count: OPC = 7281"
[1] "Cell count: Oligo = 7453"
[1] "Cell count: Endo = 2680"
[1] "Cell count: VLMC = 3373"
[1] "Cell count: Micro-PVM = 6759"
print(paste(date(),"- end"))
[1] "Sat Dec 24 19:04:26 2022 - end"
Summarize these values between matched and distinct donors.
# Function for doing this is in the extra function file
variance_stats <- list()
for (ct in subclasses){
out <- t(apply(cbind(gene_diffs_permuted[[ct]],gene_diffs[[ct]]),1,getStats))
out <- cbind(out,p.adjust(out[,6],"BH")) # Define FDR corrected p-values # qvalue(out[,6])$qvalue)#
colnames(out) <- c("permuted_var","actual_var","permuted_var_sd",
"actual_var_sd","Tstat","Pvalue","FDR_Pvalue")
variance_stats[[ct]] <- as.data.frame(out)
}
print("Summarization complete.")
[1] "Summarization complete."
Now let’s calculate and output the median variation scores for each gene. Which ones have the lowest ratio (e.g., ratio within donor tends to be dramatically lower than when random cells are takes between donors). This output will be used for
ap_summaries <- matrix(NA,nrow=dim(datAll[[1]])[1],ncol=length(subclasses))
rownames(ap_summaries) <- rownames(datAll[[1]])
colnames(ap_summaries) <- subclasses
var_real <- var_perm <- ap_summaries
for (ct in subclasses){
ap_var <- variance_stats[[ct]]$actual_var/variance_stats[[ct]]$permuted_var
ap_summaries[rownames(variance_stats[[ct]]),ct] <- ap_var
var_real[rownames(variance_stats[[ct]]),ct] <- variance_stats[[ct]]$actual_var
var_perm[rownames(variance_stats[[ct]]),ct] <- variance_stats[[ct]]$permuted_var
}
median_ap <- apply(ap_summaries,1,function(x) median(x,na.rm=TRUE))
median_real <- apply(var_real,1,median,na.rm=TRUE)
median_perm <- apply(var_perm,1,median,na.rm=TRUE)
# Write to a file
out <- data.frame(gene=names(median_ap), variation_score=median_ap,
actual_variation=median_real, permuted_variation=median_perm)
out <- out[!is.na(out$variation_score),]
write.csv(out, "variation_score.csv", row.names=FALSE)
Now directly compare distances between cells from different vs. the same donors.
plt <- as.ggplot(function(){
verboseScatterplot(median_perm,median_real,xlab="Gene distance in cells from different donor",
ylab="Gene distance in cells from same donor",pch=19,cex=0.5)
segments(0.5,0.5,5,5,lwd=2,col="grey")
text(2,4,paste0(signif(100*mean(median_perm<median_real,na.rm=TRUE),2),"% S>D"),cex=1.5)
gn <- names(head(sort(median_ap),15))
text(median_perm[gn],median_real[gn],gn,cex=0.7)
})
plt

ggsave("geneVariance_realVsPermuted_scatterplot.pdf", plot=plt, height = 6, width = 6)
Which genes per subclass are significant in each direction after FDR correction? We also get the count.
## Dynamic selection of threshold to ensure no random genes
thresh = 1
for (ct in subclasses)
thresh=min(c(thresh,variance_stats[[ct]]$FDR_Pvalue[variance_stats[[ct]]$Tstat<0]))
print(paste("Dynamic threshold:",signif(thresh,3)))
[1] "Dynamic threshold: 0.00434"
varGenes <- NULL
#thresh=0.001
for (ct in subclasses){
tmp <- abs(10*(variance_stats[[ct]]$Tstat<0)+(variance_stats[[ct]]$FDR_Pvalue<thresh)-5)-3
out <- data.frame(gene=rownames(variance_stats[[ct]]),direction=c("high","NS","low")[tmp],subclass=ct)
varGenes <- rbind(varGenes,out)
}
## Output the genes to a table
write.csv(varGenes,"variable_genes.csv",row.names = FALSE)
Plot the number.
highVar <- table(factor(varGenes$subclass[varGenes$direction=="high"],levels=subclasses))
lowVar <- table(factor(varGenes$subclass[varGenes$direction=="low"],levels=subclasses))
plt <- as.ggplot(function(){
par(mfrow=c(2,1))
par(mar=c(5,5,3,3))
barplot(highVar,las=2,xlab="",main=paste("High variance genes - P(BH.adj) <",signif(thresh,2)),col=cols[names(highVar)],ylab="Count")
barplot(lowVar,las=2,xlab="",main=paste("Low variance genes - P(BH.adj) <",signif(thresh,2)),col=cols[names(highVar)],ylab="Count")
})
plt

ggsave("variance_gene_counts_barplot.pdf", plot=plt, height = 8, width = 9)
There are a lot of genes with significantly higher than chance variation in many cell types, with essentially no genes in any cell type showing less variation than expected by chance, as expected. Many more high variance genes in glutamatergic than GABAergic types, consistent with other metrics.
What if we scale by the number of expressed genes, since non-neuronal types generally have fewer genes expressed?
geneCountPerSubclass <- table(varGenes$subclass)[names(highVar)]
plt <- as.ggplot(function(){
par(mfrow=c(2,1))
par(mar=c(5,5,3,3))
barplot(highVar/geneCountPerSubclass,las=2,xlab="", col=cols[names(highVar)],main="Fraction of expressed genes that have high variance")
abline(h=c(.1,.2,.3),lty="dashed",col="grey")
})
plt

ggsave("variance_gene_fraction_barplot.pdf", plot=plt, height = 8, width = 9)
Let’s see whether these gene counts are dependent on the number of cells per subclass per donor.
plt <- as.ggplot(function(){
par(mfrow=c(1,2))
verboseScatterplot(numberOfCells,highVar[subclasses],ylab="Number of high variance genes",
xlab="Number of total cells",main="",col=subclass_colors[names(numberOfCells)],pch=19,xlim=c(-10000,100000))
text(numberOfCells,highVar[subclasses],subclasses,cex=0.65)
verboseScatterplot(rowMeans(correct_var),highVar[subclasses],ylab="Number of high variance genes",
xlab="Mean RF prediction",main="",col=subclass_colors[names(numberOfCells)],pch=19,xlim=c(0.3,0.8))
text(rowMeans(correct_var),highVar[subclasses],subclasses,cex=0.65)
})
plt
ggsave("RF_prediction_sanityCheck_scatterplots_revisit.pdf", plot=plt, height = 6, width = 12)
# Repeat, excluding cell types with <1000 cells (we may want to do this anyway)
plt <- as.ggplot(function(){
par(mfrow=c(1,2))
kp=numberOfCells>1000
verboseScatterplot(numberOfCells[kp],highVar[subclasses][kp],ylab="Number of high variance genes",
xlab="Number of total cells",main="<1000cells",col=subclass_colors[names(numberOfCells[kp])],pch=19,xlim=c(-10000,100000))
text(numberOfCells[kp],highVar[subclasses][kp],subclasses[kp],cex=0.65)
verboseScatterplot(rowMeans(correct_var)[kp],highVar[subclasses][kp],ylab="Number of high variance genes",
xlab="Mean RF prediction",main="<1000cells",col=subclass_colors[names(numberOfCells[kp])],pch=19,xlim=c(0.3,0.8))
text(rowMeans(correct_var)[kp],highVar[subclasses][kp],subclasses[kp],cex=0.65)
})

plt

ggsave("RF_prediction_sanityCheck_scatterplots_revisitAndSubset.pdf", plot=plt, height = 6, width = 12)
Except for really rare cell types, there number of high variance genes (1) isn’t related to total number of cells but (2) is related to the mean RF prediction. We may want to consider removing these four types for the entire analysis.
Run gene ontology enrichment analysis (using fgsea) for each subclass, and then look at the results.
load(paste0(inputFolder,"GOterms.RData")) # Should have been generated previously
GOterms_all <- c(GOterms[[1]],GOterms[[2]],GOterms[[3]])
all <- unique(varGenes$gene)
goHigh <- list()
topHitsH <- NULL
for (ct in subclasses){
gnH <- varGenes$gene[(varGenes$subclass==ct)&(varGenes$direction=="high")]
goHigh[[ct]] <- fora(GOterms_all, genes=gnH, universe=all, minSize = 10, maxSize = 500)
topHitsH <- rbind(topHitsH,data.frame(goHigh[[ct]][,c(1,3)],type=ct,direction="High variance")[1:10,])
}
topHitsH[,1] <- substr(topHitsH[,1],1,40) # For legibility
topHitsH
For non-neuronal cells, the most variable genes are interestingly synaptic genes, for microglia, they are inflammatory response genes (likely reflecting microglial states), but for neuronal types there are no significant categories for nearly any subclass.
Plot some example genes as a sanity check, with each point corresponding to the average value per cell type.
ct <- "L6 CT"
plt <- as.ggplot(function(){
par(mfrow=c(1,4))
for (gn in c("XIST","LRRC37A","JUND","MALAT1")){
varx <- c(gene_diffs_permuted[[ct]][gn,],gene_diffs[[ct]][gn,])
gp <- c(rep("Different",length(gene_diffs_permuted[[ct]][gn,])),rep("Same donors",length(gene_diffs[[ct]][gn,])))
gp <- gp[!is.na(varx)]
varx <- varx[!is.na(varx)]
verboseBoxplot(varx,gp,main=gn,pt.cex=1.2, addScatterplot = TRUE, cex.lab=1.5, pch=19,
xlab="",ylab="abs(log2CPM(N1)-log2CPM(N2))",col="white",pt.col = "black")
}
})
plt

ggsave("exampleHighVarianceGenes_scatterplots.pdf", plot=plt, height = 4.5, width = 15)
Next, let’s see how frequently unique genes appear in multiple cell types as highly variable.
varCount <- table(varGenes$gene[varGenes$direction=="high"])
plt <- as.ggplot(function(){
barplot(table(varCount),log="y",las=2,ylab="# genes",col="black",
xlab="# of of subclasses for which gene is highly variable")
})
plt

ggsave("number_of_subclasses_per_high_variance_gene.pdf", plot=plt, height = 4, width = 7)
data.frame(-sort(-varCount[varCount>=15]))
data.frame(varCount[sort(names(head(sort(median_ap),15)))])
Finally, plot some comparisons between median_ap and various random forest metrics.
# Plot some gene comparisons
plt <- as.ggplot(function(){
par(mfrow=c(1,2))
gg <- intersect(rownames(gene_importances_mean),names(median_ap)[!is.na(median_ap)])
a= median_ap[gg]
b= rowMeans(gene_importances_mean,na.rm=TRUE)
names(b) <- rownames(gene_importances_mean)
b = b[gg]
verboseScatterplot(a,b,pch=19,cex=0.5,xlab="Mean gene variance ratio", ylab="Median decrease RF accuracy")
abline(0,1)
kp <- b>=0.001
text(a[kp],b[kp],names(a)[kp],cex=0.5)
a = rowMeans(male_importances_mean,na.rm=TRUE)
b = rowMeans(female_importances_mean,na.rm=TRUE)
names(a) <- names(b) <- rownames(male_importances_mean)
verboseScatterplot(a,b,pch=19,cex=0.5,xlab="Mean RF score in Male",ylab="Mean RF score in Female")
abline(0,1)
kp <- a>=0.001
text(a[kp],b[kp],names(a)[kp],cex=0.5)
})
plt

ggsave("RF_gene_importance_vs_variable_genes.pdf", plot=plt, height = 5, width = 11)
Sex chromosome genes (even y-chromosome genes!) have higher imporantance for female than male donors. I wonder if this is because there are fewer females overall in the study?
Save require files
This section saves all the variables needed for use with the SEA-AD script. This step can be skipped if you’d prefer to continue the next script without closing your working script (you could also just save everything).
save.image("input_for_seaad_analysis.RData")
Output session information.
sessionInfo()
R version 4.1.0 (2021-05-18)
Platform: x86_64-pc-linux-gnu (64-bit)
Running under: CentOS Linux 7 (Core)
Matrix products: default
BLAS/LAPACK: /usr/lib64/libopenblasp-r0.3.3.so
locale:
[1] LC_CTYPE=en_US.UTF-8 LC_NUMERIC=C LC_TIME=en_US.UTF-8 LC_COLLATE=en_US.UTF-8 LC_MONETARY=en_US.UTF-8
[6] LC_MESSAGES=en_US.UTF-8 LC_PAPER=en_US.UTF-8 LC_NAME=C LC_ADDRESS=C LC_TELEPHONE=C
[11] LC_MEASUREMENT=en_US.UTF-8 LC_IDENTIFICATION=C
attached base packages:
[1] parallel stats4 stats graphics grDevices utils datasets methods base
other attached packages:
[1] knitr_1.34 tidyr_1.1.3 dplyr_1.0.7 collapse_1.8.9 ggplot2_3.3.5
[6] ggplotify_0.1.0 extraDistr_1.9.1 pheatmap_1.0.12 randomForest_4.7-1.1 fgsea_1.18.0
[11] SingleCellExperiment_1.14.1 SummarizedExperiment_1.22.0 Biobase_2.52.0 GenomicRanges_1.44.0 GenomeInfoDb_1.28.4
[16] IRanges_2.26.0 S4Vectors_0.30.0 BiocGenerics_0.38.0 MatrixGenerics_1.4.3 matrixStats_0.61.0
[21] WGCNA_1.70-3 fastcluster_1.2.3 dynamicTreeCut_1.63-1 zellkonverter_1.2.1 SeuratObject_4.0.2
[26] Seurat_4.0.4
loaded via a namespace (and not attached):
[1] utf8_1.2.2 reticulate_1.22 tidyselect_1.1.1 RSQLite_2.2.8 AnnotationDbi_1.54.1 htmlwidgets_1.5.4
[7] grid_4.1.0 BiocParallel_1.26.2 Rtsne_0.15 munsell_0.5.0 codetools_0.2-18 ica_1.0-2
[13] preprocessCore_1.54.0 future_1.22.1 miniUI_0.1.1.1 withr_2.4.2 colorspace_2.0-2 filelock_1.0.2
[19] rstudioapi_0.13 ROCR_1.0-11 tensor_1.5 listenv_0.8.0 labeling_0.4.2 GenomeInfoDbData_1.2.6
[25] polyclip_1.10-0 bit64_4.0.5 farver_2.1.0 rprojroot_2.0.2 basilisk_1.4.0 parallelly_1.28.1
[31] vctrs_0.3.8 generics_0.1.0 xfun_0.26 R6_2.5.1 doParallel_1.0.16 bitops_1.0-7
[37] spatstat.utils_3.0-1 cachem_1.0.6 gridGraphics_0.5-1 DelayedArray_0.18.0 assertthat_0.2.1 promises_1.2.0.1
[43] scales_1.1.1 nnet_7.3-16 gtable_0.3.0 globals_0.14.0 goftest_1.2-2 rlang_0.4.11
[49] splines_4.1.0 lazyeval_0.2.2 impute_1.66.0 spatstat.geom_2.2-2 checkmate_2.0.0 reshape2_1.4.4
[55] abind_1.4-5 backports_1.2.1 httpuv_1.6.3 Hmisc_4.5-0 tools_4.1.0 ellipsis_0.3.2
[61] spatstat.core_2.3-0 RColorBrewer_1.1-2 ggridges_0.5.3 Rcpp_1.0.7 plyr_1.8.6 base64enc_0.1-3
[67] zlibbioc_1.38.0 purrr_0.3.4 RCurl_1.98-1.5 basilisk.utils_1.4.0 rpart_4.1-15 deldir_1.0-6
[73] pbapply_1.5-0 cowplot_1.1.1 zoo_1.8-9 ggrepel_0.9.1 cluster_2.1.2 here_1.0.1
[79] magrittr_2.0.1 data.table_1.14.0 scattermore_0.7 lmtest_0.9-38 RANN_2.6.1 fitdistrplus_1.1-5
[85] patchwork_1.1.1 mime_0.11 xtable_1.8-4 jpeg_0.1-9 gridExtra_2.3 compiler_4.1.0
[91] tibble_3.1.4 KernSmooth_2.23-20 crayon_1.4.1 htmltools_0.5.2 mgcv_1.8-36 later_1.3.0
[97] Formula_1.2-4 DBI_1.1.1 MASS_7.3-54 Matrix_1.3-4 cli_3.0.1 igraph_1.2.6
[103] pkgconfig_2.0.3 dir.expiry_1.0.0 foreign_0.8-81 plotly_4.9.4.1 spatstat.sparse_3.0-0 foreach_1.5.1
[109] XVector_0.32.0 yulab.utils_0.0.2 stringr_1.4.0 digest_0.6.27 sctransform_0.3.2 RcppAnnoy_0.0.19
[115] spatstat.data_2.1-0 Biostrings_2.60.2 leiden_0.3.9 fastmatch_1.1-3 htmlTable_2.2.1 uwot_0.1.10
[121] shiny_1.7.0 lifecycle_1.0.0 nlme_3.1-153 jsonlite_1.7.2 viridisLite_0.4.0 fansi_0.5.0
[127] pillar_1.6.2 lattice_0.20-45 KEGGREST_1.32.0 fastmap_1.1.0 httr_1.4.2 survival_3.2-13
[133] GO.db_3.13.0 glue_1.4.2 png_0.1-7 iterators_1.0.13 bit_4.0.4 stringi_1.7.4
[139] blob_1.2.2 latticeExtra_0.6-29 memoise_2.0.0 irlba_2.3.3 future.apply_1.8.1
LS0tCnRpdGxlOiAiUiBzY3JpcHRzIGZvciBJbnRlci1pbmRpdmlkdWFsIGdlbm9taWMgYW5kIHRyYW5zY3JpcHRvbWljIHZhcmlhdGlvbiBbLi4uXSIKYXV0aG9yOiAiSmVyZW15IE1pbGxlciIKZGF0ZTogIkRlY2VtYmVyIDIwMjIiCm91dHB1dDogaHRtbF9ub3RlYm9vawotLS0KICAKIyMgT3ZlcnZpZXcKICAKVGhpcyBub3RlYm9vayBpbmNsdWRlcyBzY3JpcHQgZm9yIGdlbmVyYXRpbmcgc29tZSBvZiB0aGUgZmlndXJlcyBhbmQgc3VwcGxlbWVudGFsIGZpZ3VyZXMgZm9yIHRoZSBtYW51c2NyaXB0OiAiSW50ZXItaW5kaXZpZHVhbCBnZW5vbWljIGFuZCB0cmFuc2NyaXB0b21pYyB2YXJpYXRpb24gaW4gaHVtYW4gY29ydGljYWwgY2VsbCB0eXBlcyIgWyhiaW9SeGl2IGxpbmspXShodHRwczovL3d3dy5iaW9yeGl2Lm9yZy9jb250ZW50LzEwLjExMDEvMjAyMi4xMC4wNy41MTEzNjZ2MS5mdWxsKS4gIAogIApQcmlvciB0byBydW5uaW5nIHRoaXMgc2NyaXB0IHBsZWFzZSBwZXJmb3JtIHRoZSBmb2xsb3dpbmcgc3RlcHM6ICAKMS4gUmV0dXJuIHRvIHRoZSBHaXRIdWIgcmVwb3NpdG9yeSB3aGVyZSB5b3UgZG93bmxvYWRlZCB0aGlzIHNjcmlwdCBbKEdpdEh1YiBsaW5rKV0oaHR0cHM6Ly9naXRodWIuY29tL0FsbGVuSW5zdGl0dXRlL2h1bWFuX3ZhcmlhdGlvbi8pLiAgCjIuIERvd25sb2FkIHRoZSAic2NyaXB0cyIgYW5kIGlucHV0X2ZpbGVzIiBmb2xkZXJzIGFuZCB0aGVpciBjb250ZXh0IHRvIHlvdXIgd29ya2luZyBkaXJlY3RvcnkuICBUaGVzZSBmb2xkZXJzIGluY2x1ZGUgZmlsZXMgbmVjZXNzYXJ5IGZvciB0aGlzIHNjcmlwdCBhbmQgZm9yICJTRUFBRF9jb21wYXJpc29uLlJtZCIuICAKMy4gRG93bmxvYWQgYWxsIG9mIHRoZSBzdWJjbGFzcy1sZXZlbCBoNWFkIGZpbGVzIGZyb20gQ0VMTHhHRU5FIHRvIGEgImRhdGFfZmlsZXMiIHN1YmZvbGRlci4gIFRoZSBsaW5rIHRvIHRoZSBjb3JyZWN0IENFTEx4R0VORSBpbnN0YW5jZSBjYW4gYmUgZm91bmQgYXQgdGhlIHNhbWUgWyhHaXRIdWIgbGluayldKGh0dHBzOi8vZ2l0aHViLmNvbS9BbGxlbkluc3RpdHV0ZS9odW1hbl92YXJpYXRpb24vKSBhYm92ZS4gIFdoZW4geW91IGFyZSBkb25lLCB0aGUgZmlsZXMgaW4gImRhdGFfZmlsZXMiIHNob3VsZCBjb3JyZXNwb25kIHRvIHRoZSBmaWxlcyBsaXN0ZWQgaW4gdGhlICJmaWxlbmFtZSIgY29sdW1hbiBvZiBzdWJjbGFzc19vcmRlci5jc3YiLiAgSWYgbm90LCBwbGVhc2UgdXBkYXRlIHRoZSBmaWxlIG5hbWVzIGluIHRoaXMgY29sdW1uIHRvIG1hdGNoLiAgCjQuIElmIG5lZWRlZCBzZXQgdGhlIHdvcmtpbmcgZGlyZWN0b3J5OiAgKGUuZy4sIGBzZXR3ZCgiUEFUSF9UT19ESVJFQ1RPUlkvb3VwdXRfZmlsZXMvIilgKS4gIChUaGlzIHNob3VsZCBoYXBwZW4gYXV0b21hdGljYWxseSwgYnV0IHNvbWV0aW1lcyBpdCBkb2Vzbid0IHdvcmsgcHJvcGVybHkgdW5sZXNzIHlvdSBtYW51YWxseSBzZXQgaXQpLiAgCiAgCk9uY2UgdGhlIGFib3ZlIGlzIGNvbXBsZXRlLCBydW4gdGhpcyBzY3JpcHQgZnJvbSBhIHBvd2VyZnVsIG1hY2hpbmUgKEkgYW0gcnVubmluZyBSU3R1ZGlvIG9uIGEgTGludXggYm94IHdpdGggMSBUQiBvZiBtZW1vcnksIGFuZCBJIHRoaW5rIH4yNTZHQiBpcyBuZWVkZWQpLiAgU3BlY2lmaWNhbGx5LCB0aGlzIHNjcmlwdCBwZXJmb3JtcyBhbmFseXNpcyBhc3NvY2lhdGVkIHdpdGggZmlndXJlcyAxRSwgMkEtQiwgMkQtRiwgUzIsIFMzLCBTNCwgUzUsIGFuZCBTNywgYW5kIHdpdGggYSBmZXcgcG9pbnRzIG5vdCByZWxhdGVkIHRvIGZpZ3VyZSBwYW5lbHMuICAKICAKIyMgUHJlcGFyZSB0aGUgZGF0YSAgCiAgCkZpcnN0IGxvYWQgdGhlIHJlcXVpcmVkIGxpYnJhcmllcyBhbmQgb3B0aW9ucyBhbmQgc2V0IGRpcmVjdG9yeSBsb2NhdGlvbnMuICAKICAKYGBge3IgbG9hZF9saWJyYXJpZXMsIHdhcm5pbmc9RkFMU0V9CiMjIExvYWQgbGlicmFyaWVzCnN1cHByZXNzUGFja2FnZVN0YXJ0dXBNZXNzYWdlcyh7CiAgbGlicmFyeShTZXVyYXQpICAgICAgICMgRm9yIGRhdGEgc3RvcmFnZSBhbmQgYW5hbHlzaXMKICBsaWJyYXJ5KHplbGxrb252ZXJ0ZXIpIyBGb3IgcmVhZGluZyBoNWFkIGZpbGVzCiAgbGlicmFyeShXR0NOQSkgICAgICAgICMgVXNlZCBmb3IgZ2VuZXJhdGluZyBzZXZlcmFsIHBsb3RzCiAgbGlicmFyeShTaW5nbGVDZWxsRXhwZXJpbWVudCkgICMgUmVxdWlyZWQgZm9yIGxvYWRpbmcgZGF0YQogIGxpYnJhcnkoZmdzZWEpICAgICAgICAjIFF1aWNrIEdPIGVucmljaG1lbnQgdGVzdAogIGxpYnJhcnkocmFuZG9tRm9yZXN0KSAjIFJhbmRvbSBmb3Jlc3QgcHJlZGljdGlvbgogIGxpYnJhcnkocGhlYXRtYXApICAgICAjIE5pY2UgcGxvdHRpbmcgZnVuY3Rpb24KICBsaWJyYXJ5KGV4dHJhRGlzdHIpICAgIyBmb3IgbXVsdGl2YXJpYXRlIGh5cGVyZ2VvbWV0cmljIGRpc3RyaWJ1dGlvbgogIGxpYnJhcnkoZ2dwbG90aWZ5KSAgICAjIEZvciBjb252ZXJ0aW5nIHJlZ3VsYXIgcGxvdHMgdG8gZ2dwbG90cyB0byBzYXZlCiAgbGlicmFyeShnZ3Bsb3QyKSAgICAgICMgRm9yIHBsb3R0aW5nIGFuZCBzYXZpbmcgcGxvdHMKICBsaWJyYXJ5KGNvbGxhcHNlKSAgICAgIyBPcHRpb25hbDogRm9yIGNhbGN1bGF0aW5nIHZhcmlhbmNlIGZhc3RlciAoY29tbWVudGVkIG9wdGlvbnMgZm9yIG5vdCB1c2luZyB0aGlzKSAjIHNob3VsZCByZXBsYWNlIGZpbmRGcm9tR3JvdXBzCiAgbGlicmFyeShkcGx5cikKICBsaWJyYXJ5KHRpZHlyKQp9KQpvcHRpb25zKHN0cmluZ3NBc0ZhY3RvcnM9RkFMU0UpCm9wdGlvbnMoZnV0dXJlLmdsb2JhbHMubWF4U2l6ZSA9IDMyMDAwICogMTAyNF4yKSAjIE5PVEUgdGhlIGxhcmdlIGFtb3VudCBvZiBtZW1vcnkgcmVxdWlyZWQgZm9yIHJ1bm5pbmcgdGhlc2Ugc2NyaXB0IQpvcHRpb25zKGZ1dHVyZS5ybmcub25NaXN1c2U9Imlnbm9yZSIpCgppZighZmlsZS5leGlzdHMoIm91dHB1dF9maWxlcyIpKSBkaXIuY3JlYXRlKCJvdXRwdXRfZmlsZXMiKQoKIyBOT1RFOiBSRVBMQUNFIFRIRSBMSU5FIEJFTE9XIFdJVEggWU9VUiBDVVJSRU5UIFdPUktJTkcgRElSRUNUT1JZCndvcmtpbmdGb2xkZXIgPC0gZ2V0d2QoKQoKaW5wdXRGb2xkZXIgICA8LSBwYXN0ZTAod29ya2luZ0ZvbGRlciwiaW5wdXRfZmlsZXMvIikKb3V0cHV0Rm9sZGVyICA8LSBwYXN0ZTAod29ya2luZ0ZvbGRlciwib3V0cHV0X2ZpbGVzLyIpCnNjcmlwdEZvbGRlciAgPC0gcGFzdGUwKHdvcmtpbmdGb2xkZXIsInNjcmlwdHMvIikKZGF0YUZvbGRlciAgICA8LSBwYXN0ZTAod29ya2luZ0ZvbGRlciwiZGF0YV9maWxlcy8iKQogIAojIyBFeHRyYSBmdW5jdGlvbnMgdG8gbGltaXQgbGlicmFyeSBsb2FkaW5nCnNvdXJjZShwYXN0ZTAoc2NyaXB0Rm9sZGVyLCJleHRyYV9mdW5jdGlvbnMuciIpKQoKc2V0d2Qob3V0cHV0Rm9sZGVyKQpgYGAKICAKYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9Cm91dHB1dEZvbGRlciAgPC0gcGFzdGUwKHdvcmtpbmdGb2xkZXIsIm91dHB1dF9maWxlcy8iKQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUpCmtuaXRyOjpvcHRzX2tuaXQkc2V0KHJvb3QuZGlyID0gZ2V0d2QoKSkKYGBgCiAgCk5leHQsIGxvYWQgdGhlIGFsbCBvZiB0aGUgcmVsZXZhbnQgZGF0YSBhbmQgbWV0YWRhdGEgZnJvbSB0aGUgcHJvamVjdCBmcm9tIHRoZSBoNWFkIGZpbGVzIGFuZCB1cGRhdGUgbWV0YWRhdGEgZmlsZSwgYW5kIHNhdmUgcmVzdWx0cyBhcyBzdWJjbGFzcy1sZXZlbCBTZXVyYXQgb2JqZWN0cy4gIAogIApgYGB7ciBsb2FkIEhWUyBGQUNTIGRhdGF9CiMjIEZvbGRlci9maWxlIHNldHVwCnN1YmNsYXNzX2ZpbGVzIDwtIHJlYWQuY3N2KHBhc3RlMChpbnB1dEZvbGRlciwic3ViY2xhc3Nfb3JkZXIuY3N2IikpCnN1YmNsYXNzZXMgPC0gc3ViY2xhc3NfZmlsZXMkc3ViY2xhc3MKZmlsZXMgPC0gc2V0TmFtZXMocGFzdGUwKGRhdGFGb2xkZXIsc3ViY2xhc3NfZmlsZXMkZmlsZW5hbWUpLHN1YmNsYXNzZXMpCgojIyBSZWFkIGluIHN1YmNsYXNzIGFuZCBjZWxsIHR5cGUgY29sb3JzCnR5cGVfaW5mbyA8LSByZWFkLmNzdihwYXN0ZTAoaW5wdXRGb2xkZXIsImNsdXN0ZXJfb3JkZXJfYW5kX2NvbG9ycy5jc3YiKSkKdHlwZV9pbmZvJHN1YmNsYXNzX2xhYmVsIDwtIHN1YmNsYXNzX2ZpbGVzJHN1YmNsYXNzW21hdGNoKHR5cGVfaW5mbyRzdWJjbGFzc19sYWJlbCxzdWJjbGFzc19maWxlcyRzdWJjbGFzc19TRUFBRCldCnN1YmNsYXNzX2NvbG9ycyA8LSBzZXROYW1lcyh0eXBlX2luZm8kc3ViY2xhc3NfY29sb3JbbWF0Y2goc3ViY2xhc3Nlcyx0eXBlX2luZm8kc3ViY2xhc3NfbGFiZWwpXSxzdWJjbGFzc2VzKQoKIyMgUmVhZCBpbiB0aGUgZGF0YSwgcGlwaW5nIHRoZSB3YXJuaW5ncyB0byBhIGZpbGUKZGF0QWxsIDwtIGxpc3QoKQp6eiA8LSBmaWxlKCJhbGwudHh0Iiwgb3Blbj0id3QiKSAjIFNlbmQgd2FybmluZyBtZXNzYWdlcyB0byBhIGZpbGUgc28gYXZvaWQgY3Jhc2hpbmcgUgpzaW5rKHp6LCB0eXBlPSJtZXNzYWdlIikKIyMgUmVhZCBpbiBhbGwgb2YgdGhlIGRhdGEKZm9yIChzIGluIHN1YmNsYXNzZXMpewogIHByaW50KHMpCiAgdG1wIDwtIHJlYWRINUFEKGZpbGVzW3NdKQogIHNldSA8LSBDcmVhdGVTZXVyYXRPYmplY3QoY291bnRzPWFzc2F5KHRtcCwiVU1JcyIpKQogIG1ldCA8LSBhcy5kYXRhLmZyYW1lKGNvbERhdGEodG1wKSkKICBzZXUgPC0gU2V0QXNzYXlEYXRhKHNldSxuZXcuZGF0YT1sb2dDUE0oc2V1QGFzc2F5cyRSTkFAY291bnRzKSxzbG90PSJkYXRhIikKICAjc2V1IDwtIFNldEFzc2F5RGF0YShzZXUsbmV3LmRhdGE9YXNzYXkodG1wLCJzY3ZpX25vcm1hbGl6ZWQiKSxzbG90PSJzY2FsZS5kYXRhIikgIyBOb3QgdXNlZAogIHNldSA8LSBBZGRNZXRhRGF0YShzZXUsbWV0KQogIGRhdEFsbFtbc11dIDwtIHNldQp9CnNpbmsodHlwZT0ibWVzc2FnZSIpCmNsb3NlKHp6KQphPWZpbGUucmVtb3ZlKCJhbGwudHh0IikKYGBgICAKICAKICAKVXBkYXRlIG1ldGFkYXRhIGFuZCBRQyBmaWx0ZXJpbmcgKGtlZXAgYWxsIGNlbGxzIHdpdGggZXhjbHVkZTI9Tm8gKyBRQzIgPVRSVUUpLiAgQWxzbyBleGNsdWRlIGRvbm9ycyBub3QgcGFydCBvZiB0aGlzIHN0dWR5LiAgCiAgCmBgYHtyIGRvbm9yIG1ldGFkYXRhfQojIyBSZWFkIGluIHRoZSBkb25vciBtZXRhZGF0YQpkb25vcl9jb2x1bW5zICA8LSBjKCJST0kiLCJTZXgiLCJBZ2UiLCJEaXNlYXNlIiwiVGlzc3VlLlNvdXJjZSIsICJBbmNlc3RyeSIpCmRvbm9yX21ldGFkYXRhIDwtIHJlYWQuY3N2KHBhc3RlMChpbnB1dEZvbGRlciwiaHVtYW5fdmFyaWF0aW9uX2ZpbGVfbWFuaWZlc3RfMjIwNjI0LmNzdiIpLCByb3cubmFtZXMgPSAxKQpjb250cm9sX2Rvbm9ycyA8LSBjKCJIMTguMzAuMDAyIiwgIkgxOS4zMC4wMDIiLCAiSDIwMC4xMDIzIiwgIkgxOC4zMC4wMDEiLCAiSDE5LjMwLjAwMSIpCmh2c19kb25vcnMgICAgIDwtIHJvd25hbWVzKGRvbm9yX21ldGFkYXRhKVtkb25vcl9tZXRhZGF0YSR1c2VdICAjIE5vdGUgdGhlIGxvd2VyLWNhc2UgInUiIGluICJ1c2UiLiAgSW4gdGhpcyBjYXNlLCBhbGwgb2YgdGhlIHJvd3MgZWl0aGVyIGNvcnJlc3BvbmQgdG8gdGhlIHBhdGllbnRzIHVuZGVyZ29pbmcgbmV1cm9zdXJnZXJ5IHRoYXQgYXJlIHBhcnQgb2YgdGhpcyBzdHVkeSwgb3IgdGhlIGZpdmUgY29udHJvbCBkb25vcnMgbGlzdGVkIGFib3ZlLgpkb25vcl9tZXRhZGF0YSA8LSBkb25vcl9tZXRhZGF0YVssZG9ub3JfY29sdW1uc10KZG9ub3JzICAgICAgICAgPC0gYyhodnNfZG9ub3JzLGNvbnRyb2xfZG9ub3JzKQoKIyMgVXBkYXRlIHRoZSBtZXRhZGF0YSAKZm9yIChzIGluIHN1YmNsYXNzZXMpewogIHRlbXBfbWV0YWRhdGEgPC0gZG9ub3JfbWV0YWRhdGFbZGF0QWxsW1tzXV1AbWV0YS5kYXRhJGRvbm9yX25hbWUsXQogIGZvciAoY29sIGluIGRvbm9yX2NvbHVtbnMpCiAgICBkYXRBbGxbW3NdXSA8LSBBZGRNZXRhRGF0YShkYXRBbGxbW3NdXSx0ZW1wX21ldGFkYXRhWyxjb2xdLGNvbCkKICBkYXRBbGxbW3NdXUBtZXRhLmRhdGEkZXhjbHVkZTJbaXMubmEoZGF0QWxsW1tzXV1AbWV0YS5kYXRhJGV4Y2x1ZGUyKV0gPSAiTm8iCiAgcWNfcGFzcyAgICAgICA8LSAoZGF0QWxsW1tzXV1AbWV0YS5kYXRhJGV4Y2x1ZGUyPT0iTm8iKSYoZGF0QWxsW1tzXV1AbWV0YS5kYXRhJFFDMl9tZXRhY2VsbF9jbGFzc19mbGFnIT0iRmFsc2UiKSAKICBxY19wYXNzICAgICAgIDwtIHFjX3Bhc3N8aXMuZWxlbWVudChkYXRBbGxbW3NdXSRkb25vcl9uYW1lLGNvbnRyb2xfZG9ub3JzKQogIGRhdEFsbFtbc11dICAgPC0gQWRkTWV0YURhdGEoZGF0QWxsW1tzXV0scWNfcGFzcywicWNfcGFzcyIpCiAgZGF0QWxsW1tzXV0gICA8LSBkYXRBbGxbW3NdXVssaXMuZWxlbWVudChkYXRBbGxbW3NdXSRkb25vcl9uYW1lLGRvbm9ycykmcWNfcGFzc10gIAp9CmBgYAogIApBdCB0aGlzIHBvaW50LCBhbGwgcmVtYWluaW5nIGNlbGxzIGFuZCBkb25vcnMgaW4gdGhlIGRhdGEgc2V0IGhhdmUgcGFzc2VkIFFDIGFuZCB3aWxsIGJlIHVzZWQgZm9yIHRoZSByZW1haW5kZXIgb2YgdGhlIGFuYWx5c2VzLiAgCiAgCiAgCiMjIENvbXBhcmlzb24gb2YgZ2VuZXMgZXhwcmVzc2VkIGFuZCBhYnVuZGFuY2VzCiAgClRoaXMgc2VjdGlvbiBwZXJmb3JtcyBhIHZhcmlldHkgb2YgYW5hbHlzZXMgcmVsYXRlZCB0byBvdmVyYWxsIGdlbmUgZXhwcmVzc2lvbiBsZXZlbHMgKGUuZy4sIG51bWJlciBvZiBnZW5lcyBleHByZXNzZWQgcGVyIGRvbm9yKSBhbmQgY2VsbCB0eXBlIGFidW5kYW5jZXMgKGUuZy4sIG51bWJlciBvZiBjZWxscyBtYXBwZWQgdG8gZWFjaCBzdWJjbGFzcyBmb3IgZWFjaCBkb25vcikuIFdlIHN0YXJ0IGJ5IGNhbGN1bGF0aW5nIHRoZSBhYnVuZGFuY2UgdmFyaWFuY2UgYWNyb3NzIGRvbm9yLCBhbmQgc2NhbGUgYnkgY2xhc3MuICBXZSdsbCBhbHNvIGxvb2sgYXQgdGhlIG51bWJlciBvZiBnZW5lcyB3aXRoIGV4cHJlc3Npb24gaW4gYXQgbGVhc3QgMjUlIG9mIGNlbGxzIGZvciBhIGdpdmVuIGNlbGwgdHlwZSBwZXIgZG9ub3IgKGUuZy4sIHRyaW1tZWQgbWVhbnMgPiAwKS4gIAogIApMZXQncyBzZXQgdXAgdmFyaWFibGVzIGluIHRoaXMgc2VjdGlvbi4gIAogIApgYGB7ciBnZW5lcyBhbmQgYWJ1bmRhbmNlIHNldHVwfQojIEdldCBnZW5lIGFuZCBjZWxsIGNvdW50cyBwZXIgc3ViY2xhc3MKZ2VuZUNvdW50cyA8LSBjZWxsQ291bnRzIDwtIE5VTEwKdG1lYW5FeHByICA8LSBsaXN0KCkKZm9yIChzIGluIHN1YmNsYXNzZXMpewogIHByaW50KHMpCiAgZG9ucyAgPC0gZmFjdG9yKGFzLmNoYXJhY3RlcihkYXRBbGxbW3NdXSRkb25vcl9uYW1lKSxsZXZlbHM9ZG9ub3JzKQogIGNlbGxzIDwtIHRhYmxlKGRvbnMpCiAgY2VsbENvdW50cyA8LSBjYmluZChjZWxsQ291bnRzLGNlbGxzKQogIHRtZWFuRXhwcltbc11dIDwtIGZpbmRGcm9tR3JvdXBzKGRhdEFsbFtbc11dQGFzc2F5cyRSTkFAZGF0YSxkb25zLHRtZWFuKSAjIGNvbGxhcHNlIHBhY2thZ2UgaGFzIGZhc3RlciBtZWFuIGFuZCBtZWRpYW4sIGJ1dCBub3QgdHJpbW1lZCBtZWFuCiAgZ2VuZUNvdW50cyA8LSBjYmluZChnZW5lQ291bnRzLGFzLm51bWVyaWMoY29sU3Vtcyh0bWVhbkV4cHJbW3NdXT4wKSkpCn0KY29sbmFtZXMoZ2VuZUNvdW50cykgPC0gY29sbmFtZXMoY2VsbENvdW50cykgPC0gc3ViY2xhc3NlcwpnZW5lQ291bnRzW2dlbmVDb3VudHM9PTBdID0gTkEKcm93bmFtZXMoZ2VuZUNvdW50cykgPC0gcm93bmFtZXMoY2VsbENvdW50cykKCiMgU2V0IHVwIHZhcmlhYmxlcyBmb3Igc2NhbGluZyB0byBjbGFzcwpjbGFzc2VzIDwtIGMoIkdBQkFlcmdpYyIsIkdsdXRhbWF0ZXJnaWMiLCJOb24tbmV1cm9uYWwiKQpjZWxsQ2xhc3MgPC0gbWF0cml4KDAsbnJvdz1kaW0oZ2VuZUNvdW50cylbMV0sbmNvbD0zKQpjb2xuYW1lcyhjZWxsQ2xhc3MpIDwtIGNsYXNzZXMKcm93bmFtZXMoY2VsbENsYXNzKSA8LSByb3duYW1lcyhnZW5lQ291bnRzKQp1bWlDbGFzcyA8LSBnZW5lc0NsYXNzIDwtIGNlbGxDbGFzcwpjbGFzc0dyb3VwcyA8LSBsaXN0KHN1YmNsYXNzZXNbMTo5XSxzdWJjbGFzc2VzWzEwOjE4XSxzdWJjbGFzc2VzWzE5OjI0XSkKbmFtZXMoY2xhc3NHcm91cHMpIDwtIGNsYXNzZXMKCiMgR2V0IGNlbGwgY291bnRzIHBlciBzdWJjbGFzcwpjZWxsQ291bnRzTiA8LSBjZWxsQ291bnRzKjAKZm9yIChsIGluIGRvbm9ycyl7CiAgZm9yIChjbCBpbiBjbGFzc2VzKXsKICAgIGtwIDwtIGlzLmVsZW1lbnQoc3ViY2xhc3NlcyxjbGFzc0dyb3Vwc1tbY2xdXSkKICAgIGNlbGxDb3VudHNOW2wsY2xhc3NHcm91cHNbW2NsXV1dIDwtIGNlbGxDb3VudHNbbCxjbGFzc0dyb3Vwc1tbY2xdXV0vc3VtKGNlbGxDb3VudHNbbCxjbGFzc0dyb3Vwc1tbY2xdXV0pCiAgfQp9CmBgYAogIAogIAojIyMgU3RhdGlzdGljcyBieSBkb25vcgogIApQbG90IHZhcmlhdGlvbiBpbiBhYnVuZGFuY2VzIGFjcm9zcyBkb25vcnMgYnkgc3ViY2xhc3MuICAKCmBgYHtyIGFidW5kYW5jZSB2YWx1ZXMsIGZpZy5oZWlnaHQ9NixmaWcud2lkdGg9OH0KbGlicmFyeShrbml0cikKcHJpbnQob3B0c19jdXJyZW50JGdldCgpJGxhYmVsKQp2ZWN0b3JBYnVuZGFuY2UgPC0gdW5saXN0KGRhdGEuZnJhbWUoY2VsbENvdW50c05baHZzX2Rvbm9ycyxdKSkKdmVjdG9yU3ViY2xhc3MgIDwtIE5VTEwKZm9yIChzIGluIHN1YmNsYXNzZXMpIHZlY3RvclN1YmNsYXNzIDwtIGModmVjdG9yU3ViY2xhc3MscmVwKHMsbGVuZ3RoKGh2c19kb25vcnMpKSkKdmVjdG9yQ29scyA8LSB0eXBlX2luZm8kc3ViY2xhc3NfY29sb3JbbWF0Y2godmVjdG9yU3ViY2xhc3MsdHlwZV9pbmZvJHN1YmNsYXNzX2xhYmVsKV0KdmVjdG9yU3ViY2xhc3MgPC0gZmFjdG9yKHZlY3RvclN1YmNsYXNzLGxldmVscz1zdWJjbGFzc2VzKQoKcGx0IDwtIGFzLmdncGxvdChmdW5jdGlvbigpewpwYXIobWFyPWMoMTAsNSw1LDUpKQp2ZXJib3NlQmFycGxvdCgxMDAqdmVjdG9yQWJ1bmRhbmNlKzEsdmVjdG9yU3ViY2xhc3MsbWFpbj0iIix5bGFiPSJBYnVuZGFuY2UiLHhsYWI9IiIsIHlsaW0gPSBjKDAsMTAwKSwKICAgICAgICAgICAgICAgICBhZGRTY2F0dGVycGxvdCA9IFRSVUUsIHBjaD0xOSwgcHQuY2V4PTAuNSwgcHQuY29sID0gdmVjdG9yQ29scywgbGFzPTIsIGNvbD0id2hpdGUiKQp9KQpwbHQKZ2dzYXZlKCJhYnVuZGFuY2VfYmFycGxvdHMucGRmIiwgcGxvdD1wbHQsIGhlaWdodCA9IDYsIHdpZHRoID0gOCkKYGBgCiAgClNpbmNlIGFidW5kYW5jZSB2YWx1ZXMgYXJlIGluaGVyZW50bHkgcmVsYXRlZCAoZS5nLiwgaW5jcmVhc2UgaW4gT1BDcyBtZWFucyBkZWNyZWFzZSBpbiBvdGhlciBnbGlhIGJ5IGRlZmluaXRpb24pIGl0IGlzIHVzZWZ1bCB0byBzZWUgaG93IHRoZXNlIHByb3BvcnRpb25zIGFyZSBjb3JyZWxhdGVkIGFjcm9zcyBkb25vcnMuICBMZXQncyBwZXJmb3JtIHRoaXMgYW5hbHlzaXMuICAKICAKYGBge3IgY29ycmVsYXRlIHN1YmNsYXNzIGFidW5kYW5jZXMsIGZpZy5oZWlnaHQ9NS41LCBmaWcud2lkdGg9Nn0KYWJ1bmRhbmNlQ29yIDwtIGNvcihjZWxsQ291bnRzTltodnNfZG9ub3JzLF0pCmFiID0gcGhlYXRtYXAoYWJ1bmRhbmNlQ29yKSMsY2x1c3Rlcl9jb2xzID0gRkFMU0UsIGNsdXN0ZXJfcm93cyA9IEZBTFNFKQphYgpnZ3NhdmUoImFidW5kYW5jZV9zdWJjbGFzc19jb3JyZWxhdGlvbnMucGRmIiwgcGxvdD1hYiwgaGVpZ2h0ID0gNS41LCB3aWR0aCA9IDYpCmBgYAoKICAKUGxvdCB2YXJpYXRpb24gaW4gZ2VuZSBjb3VudHMgYWNyb3NzIGRvbm9ycyBieSBzdWJjbGFzcy4gIAoKYGBge3IgZ2VuZXMgY291bnRzIHZhbHVlcywgZmlnLmhlaWdodD02LGZpZy53aWR0aD04fQp2ZWN0b3JHZW5lcyA8LSB1bmxpc3QoZGF0YS5mcmFtZShnZW5lQ291bnRzW2h2c19kb25vcnMsXSkpCgpwbHQgPC0gYXMuZ2dwbG90KGZ1bmN0aW9uKCl7CnBhcihtYXI9YygxMCw1LDUsNSkpCnZlcmJvc2VCYXJwbG90KHZlY3RvckdlbmVzLzEwMDAsdmVjdG9yU3ViY2xhc3MsbWFpbj0iIix5bGFiPSJHZW5lcyBleHByZXNzZWQgKEspIix4bGFiPSIiLHlsaW09YygwLDE1KSwKICAgICAgICAgICAgICAgYWRkU2NhdHRlcnBsb3QgPSBUUlVFLCBwY2g9MTksIHB0LmNleD0wLjUsIHB0LmNvbCA9IHZlY3RvckNvbHMsIGxhcz0yLCBjb2w9IndoaXRlIikKfSkKcGx0Cmdnc2F2ZSgiZ2VuZV9jb3VudHNfYmFycGxvdHMucGRmIiwgcGxvdD1wbHQsIGhlaWdodCA9IDYsIHdpZHRoID0gOCkKYGBgCiAgCkZvciBhIGZldyBkb25vcnMsIHRoZSBudW1iZXIgb2YgZ2VuZXMgZXhwcmVzc2VkIGluIG5vbi1uZXVyb25hbCBjZWxscyBpcyBleGNlcHRpb25hbGx5IGhpZ2gsIGxpa2VseSByZWZsZWN0aW5nIHNvbWUgaXNzdWVzIGluIFFDaW5nIGRvdWJsZXRzIG9yIG1hcHBpbmcuICBGb3Igbm93LCBsZXQncyBub3Qgd29ycnkgYWJvdXQgdGhpcy4gICAgCiAgCiAgCiMjIyBTdGF0aXN0aWNzIGJ5IGRpc2Vhc2UgIAogIApMZXQncyBub3cgbG9vayBhdCBjZWxsIHR5cGUgYWJ1bmRhbmNlcyBhbmQgZ2VuZSBjb3VudHMgd2hlbiBjb21wYXJpbmcgZXBpbGVwc3kgdnMuIHR1bW9yLiAgCiAgCmBgYHtyIGNlbGwgdHlwZSBhYnVuZGFuY2VzIG5vcm1hbGl6ZWQgYnkgZGlzZWFzZSwgZmlnLmhlaWdodD04LGZpZy53aWR0aD0xMH0KIyBFeGNsdWRlIHR3byBkb25vcnMgd2l0aCBib3RoIGNvbmRpdGlvbnMKa3BfZG9ub3JzIDwtIGludGVyc2VjdChodnNfZG9ub3JzLHJvd25hbWVzKGRvbm9yX21ldGFkYXRhKVtpcy5lbGVtZW50KGRvbm9yX21ldGFkYXRhJERpc2Vhc2UsYygiRXBpbGVwc3kiLCJUdW1vciIpKV0pCmFsbEdyb3VwICA8LSBmYWN0b3IoZG9ub3JfbWV0YWRhdGFba3BfZG9ub3JzLCJEaXNlYXNlIl0sbGV2ZWxzID0gYygiRXBpbGVwc3kiLCJUdW1vciIpKQoKIyBNYWtlIHBsb3RzCnBsdCA8LSBhcy5nZ3Bsb3QoZnVuY3Rpb24oKXsKcGFyKG1mcm93PWMoNCw2KSkKcGFyKG1hcj1jKDIsNCwyLDEpKQpmb3IgKHMgaW4gc3ViY2xhc3NlcykKICB2ZXJib3NlQmFycGxvdChjZWxsQ291bnRzTltrcF9kb25vcnMsc10sYWxsR3JvdXAsbWFpbj0iIix5bGFiPXMscHQuY29sPXN1YmNsYXNzX2NvbG9yc1tzXSxjb2w9IndoaXRlIiwKICAgICAgICAgICAgICAgICB4bGFiPSIiLHlsaW09YygwLG1heChjZWxsQ291bnRzTltrcF9kb25vcnMsc10pKjEuMDIpLAogICAgICAgICAgICAgICAgIGFkZFNjYXR0ZXJwbG90ID0gVFJVRSwgcGNoPTE5LCBwdC5jZXg9MSwgY2V4LmxhYj0xLCBjZXguYXhpcyA9IDAuNywgY2V4Lm1haW4gPSAxKQp9KQpwbHQKZ2dzYXZlKCJhYnVuZGFuY2VfYmFycGxvdHNfYnlfc3ViY2xhc3MucGRmIiwgcGxvdD1wbHQsIGhlaWdodCA9IDgsIHdpZHRoID0gMTApCmBgYAogIApUaGUgb25seSBzaWduaWZpY2FudCBkaWZmZXJlbmNlIGlzIGEgZGVjcmVhc2UgaW4gUFZBTEIrIGludGVybmV1cm9ucyBpbiBlcGlsZXBzeSBjYXNlcywgd2hpY2ggbWFrZXMgY29tcGxldGUgc2Vuc2UhICAKICAKTmV4dCB3ZSBjb21wYXJlIHRoZSBudW1iZXIgb2YgZ2VuZXMgZXhwcmVzc2VkIHBlciBzdWJjbGFzcyBpbiBlcGlsZXBzeSB2cy4gdHVtb3IuICAKCmBgYHtyIGdlbmVzIGRldGVjdGVkIHBlciBjZWxsIGJ5IGRpc2Vhc2UsIGZpZy5oZWlnaHQ9OCxmaWcud2lkdGg9MTB9CiMgTWFrZSBwbG90cwpwbHQgPC0gYXMuZ2dwbG90KGZ1bmN0aW9uKCl7CnBhcihtZnJvdz1jKDQsNikpCnBhcihtYXI9YygyLDQsMiwxKSkKZm9yIChzIGluIHN1YmNsYXNzZXMpCiAgdmVyYm9zZUJhcnBsb3QoZ2VuZUNvdW50c1trcF9kb25vcnMsc10vMTAwMCxhbGxHcm91cCxtYWluPSIiLHlsYWI9cGFzdGUocywiKHRob3VzYW5kcykiKSx4bGFiPSIiLCAKICAgICAgICAgICAgICAgICB5bGltID0gYygwLDE1KSxwdC5jb2w9c3ViY2xhc3NfY29sb3JzW3NdLGNvbD0id2hpdGUiLAogICAgICAgICAgICAgICAgIGFkZFNjYXR0ZXJwbG90ID0gVFJVRSwgcGNoPTE5LCBwdC5jZXg9MSwgY2V4LmxhYj0xLCBjZXguYXhpcyA9IDAuNywgY2V4Lm1haW4gPSAxKQp9KQpwbHQKZ2dzYXZlKCJnZW5lQ291bnRfYmFycGxvdHNfYnlfc3ViY2xhc3MucGRmIiwgcGxvdD1wbHQsIGhlaWdodCA9IDgsIHdpZHRoID0gMTApCmBgYAogIApUaGVyZSBpcyBubyBkaWZmZXJlbmNlIGJldHdlZW4gdHVtb3IgYW5kIGVwaWxlcHN5IGF0IHRoaXMgbGV2ZWwuICAKICAKICAKIyMjIFN0YXRpc3RpY3MgYnkgc2V4ICAKICAKTm93IGxldCdzIHNlZSBpZiB0aGVyZSBhcmUgYW55IGRpZmZlcmVuY2VzIGluIGFidW5kYW5jZSBvZiBnZW5lIGV4cHJlc3Npb24gaW4gbWFsZXMgdnMuIGZlbWFsZXMuICAKICAKYGBge3IgY2VsbCB0eXBlIGFidW5kYW5jZXMgYW5kIGdlbmUgZXhwcmVzc2lvbiBub3JtYWxpemVkIGJ5IHNleCwgZmlnLmhlaWdodD04LGZpZy53aWR0aD0xMH0KbWZHcm91cCAgPC0gZmFjdG9yKGRvbm9yX21ldGFkYXRhW2h2c19kb25vcnMsIlNleCJdKQpwbHQgPC0gYXMuZ2dwbG90KGZ1bmN0aW9uKCl7CnBhcihtZnJvdz1jKDQsNikpCnBhcihtYXI9YygyLDQsMiwxKSkKZm9yIChzIGluIHN1YmNsYXNzZXMpCiAgdmVyYm9zZUJhcnBsb3QoY2VsbENvdW50c05baHZzX2Rvbm9ycyxzXSxtZkdyb3VwLG1haW49IiIseWxhYj1zLHB0LmNvbD1zdWJjbGFzc19jb2xvcnNbc10sCiAgICAgICAgICAgICAgICAgeGxhYj0iIix5bGltPWMoMCxtYXgoY2VsbENvdW50c05baHZzX2Rvbm9ycyxzXSkqMS4wMiksY29sPSJ3aGl0ZSIsCiAgICAgICAgICAgICAgICAgYWRkU2NhdHRlcnBsb3QgPSBUUlVFLCBwY2g9MTksIHB0LmNleD0xLCBjZXgubGFiPTEsIGNleC5heGlzID0gMC43LCBjZXgubWFpbiA9IDEpCn0pCnBsdApnZ3NhdmUoImFidW5kYW5jZV9iYXJwbG90c19ieV9zZXgucGRmIiwgcGxvdD1wbHQsIGhlaWdodCA9IDgsIHdpZHRoID0gMTApCgpwbHQgPC0gYXMuZ2dwbG90KGZ1bmN0aW9uKCl7CnBhcihtZnJvdz1jKDQsNikpCnBhcihtYXI9YygyLDQsMiwxKSkKZm9yIChzIGluIHN1YmNsYXNzZXMpCiAgdmVyYm9zZUJhcnBsb3QoZ2VuZUNvdW50c1todnNfZG9ub3JzLHNdLzEwMDAsbWZHcm91cCxtYWluPSIiLHlsYWI9cGFzdGUocywiKHRob3VzYW5kcykiKSx4bGFiPSIiLCAKICAgICAgICAgICAgICAgICB5bGltID0gYygwLDE1KSxwdC5jb2w9c3ViY2xhc3NfY29sb3JzW3NdLGNvbD0id2hpdGUiLAogICAgICAgICAgICAgICAgIGFkZFNjYXR0ZXJwbG90ID0gVFJVRSwgcGNoPTE5LCBwdC5jZXg9MSwgY2V4LmxhYj0xLCBjZXguYXhpcyA9IDAuNywgY2V4Lm1haW4gPSAxKQp9KQpwbHQKZ2dzYXZlKCJnZW5lQ291bnRfYmFycGxvdHNfYnlfc2V4LnBkZiIsIHBsb3Q9cGx0LCBoZWlnaHQgPSA4LCB3aWR0aCA9IDEwKQpgYGAKICAKTm8gc2lnbmlmaWNhbnQgZGlmZmVyZW5jZXMgYnkgc2V4LiAgCiAgCiAgCiMjIyBTdGF0aXN0aWNzIGJ5IEFnZSAgCiAgCk5vdyBsZXQncyBzZWUgaWYgdGhlcmUgYXJlIGFueSBkaWZmZXJlbmNlcyBpbiBhYnVuZGFuY2Ugb2YgZ2VuZSBleHByZXNzaW9uIGJ5IGFnZS4gIAogIApgYGB7ciBjZWxsIHR5cGUgYWJ1bmRhbmNlcyBhbmQgZ2VuZSBleHByZXNzaW9uIG5vcm1hbGl6ZWQgYnkgYWdlLCBmaWcuaGVpZ2h0PTgsZmlnLndpZHRoPTE4fQphZ2VWZWN0b3IgIDwtIGFzLm51bWVyaWMoZG9ub3JfbWV0YWRhdGFbaHZzX2Rvbm9ycywiQWdlIl0pCnBsdCA8LSBhcy5nZ3Bsb3QoZnVuY3Rpb24oKXsKcGFyKG1mcm93PWMoNCw2KSkKcGFyKG1hcj1jKDIsNCwyLDEpKQpmb3IgKHMgaW4gc3ViY2xhc3NlcykKICB2ZXJib3NlU2NhdHRlcnBsb3QoYWdlVmVjdG9yLGNlbGxDb3VudHNOW2h2c19kb25vcnMsc10sbWFpbj0iIix5bGFiPXMsCiAgICAgICAgICAgICAgICAgeGxhYj0iIix5bGltPWMoMCxtYXgoY2VsbENvdW50c05baHZzX2Rvbm9ycyxzXSkqMS4wMiksY29sPXN1YmNsYXNzX2NvbG9yc1tzXSwKICAgICAgICAgICAgICAgICBwY2g9MTksIGNleC5sYWI9MS4xLCBjZXguYXhpcyA9IDAuOCwgY2V4Lm1haW4gPSAxLjI1LCBhYmxpbmU9VFJVRSkKfSkKcGx0Cmdnc2F2ZSgiYWJ1bmRhbmNlX3NjYXR0ZXJwbG90c19ieV9BZ2UucGRmIiwgcGxvdD1wbHQsIGhlaWdodCA9IDgsIHdpZHRoID0gMTgpCgpwbHQgPC0gYXMuZ2dwbG90KGZ1bmN0aW9uKCl7CnBhcihtZnJvdz1jKDQsNikpCnBhcihtYXI9YygyLDQsMiwxKSkKZm9yIChzIGluIHN1YmNsYXNzZXMpCiAgdmVyYm9zZVNjYXR0ZXJwbG90KGFnZVZlY3RvcixnZW5lQ291bnRzW2h2c19kb25vcnMsc10vMTAwMCxtYWluPSIiLHlsYWI9cywKICAgICAgICAgICAgICAgICB4bGFiPSIiLHlsaW09YygwLDE1KSxjb2w9c3ViY2xhc3NfY29sb3JzW3NdLAogICAgICAgICAgICAgICAgIHBjaD0xOSwgY2V4LmxhYj0xLjEsIGNleC5heGlzID0gMC44LCBjZXgubWFpbiA9IDEuMjUsIGFibGluZT1UUlVFKQp9KQpwbHQKZ2dzYXZlKCJnZW5lQ291bnRfc2NhdHRlcnBsb3RzX2J5X0FnZS5wZGYiLCBwbG90PXBsdCwgaGVpZ2h0ID0gOCwgd2lkdGggPSAxOCkKYGBgCiAgCiMjIyBTdGF0aXN0aWNzIGJ5IFJPSSAgCiAgCk5vdyBsZXQncyBzZWUgaWYgdGhlcmUgYXJlIGFueSBkaWZmZXJlbmNlcyBpbiBhYnVuZGFuY2Ugb2YgZ2VuZSBleHByZXNzaW9uIGJ5IGJyYWluIHJlZ2lvbi4gIAogIApgYGB7ciBjZWxsIHR5cGUgYWJ1bmRhbmNlcyBhbmQgZ2VuZSBleHByZXNzaW9uIG5vcm1hbGl6ZWQgYnkgUk9JLCBmaWcuaGVpZ2h0PTgsZmlnLndpZHRoPTE0fQpyb2lHcm91cCAgPC0gZmFjdG9yKGRvbm9yX21ldGFkYXRhW2h2c19kb25vcnMsIlJPSSJdKQpwbHQgPC0gYXMuZ2dwbG90KGZ1bmN0aW9uKCl7CnBhcihtZnJvdz1jKDQsNikpCnBhcihtYXI9YygyLDQsMiwxKSkKZm9yIChzIGluIHN1YmNsYXNzZXMpCiAgdmVyYm9zZUJhcnBsb3QoY2VsbENvdW50c05baHZzX2Rvbm9ycyxzXSxyb2lHcm91cCxtYWluPSIiLHlsYWI9cywKICAgICAgICAgICAgICAgICB4bGFiPSIiLHlsaW09YygwLG1heChjZWxsQ291bnRzTltodnNfZG9ub3JzLHNdKSoxLjAyKSxwdC5jb2w9c3ViY2xhc3NfY29sb3JzW3NdLGNvbD0id2hpdGUiLAogICAgICAgICAgICAgICAgIGFkZFNjYXR0ZXJwbG90ID0gVFJVRSwgcGNoPTE5LCBwdC5jZXg9MSwgY2V4LmxhYj0xLCBjZXguYXhpcyA9IDAuNywgY2V4Lm1haW4gPSAxLjUpCn0pCnBsdApnZ3NhdmUoImFidW5kYW5jZV9iYXJwbG90c19ieV9ST0kucGRmIiwgcGxvdD1wbHQsIGhlaWdodCA9IDgsIHdpZHRoID0gMTQpCgpwbHQgPC0gYXMuZ2dwbG90KGZ1bmN0aW9uKCl7CnBhcihtZnJvdz1jKDQsNikpCnBhcihtYXI9YygyLDQsMiwxKSkKZm9yIChzIGluIHN1YmNsYXNzZXMpCiAgdmVyYm9zZUJhcnBsb3QoZ2VuZUNvdW50c1todnNfZG9ub3JzLHNdLzEwMDAscm9pR3JvdXAsbWFpbj0iIix5bGFiPXBhc3RlKHMsIih0aG91c2FuZHMpIikseGxhYj0iIiwKICAgICAgICAgICAgICAgICB5bGltID0gYygwLDE1KSxwdC5jb2w9c3ViY2xhc3NfY29sb3JzW3NdLGNvbD0id2hpdGUiLAogICAgICAgICAgICAgICAgIGFkZFNjYXR0ZXJwbG90ID0gVFJVRSwgcGNoPTE5LCBwdC5jZXg9MSwgY2V4LmxhYj0xLCBjZXguYXhpcyA9IDAuNywgY2V4Lm1haW4gPSAxLjUpCn0pCnBsdApnZ3NhdmUoImdlbmVDb3VudF9iYXJwbG90c19ieV9ST0kucGRmIiwgcGxvdD1wbHQsIGhlaWdodCA9IDgsIHdpZHRoID0gMTQpCmBgYAogIApUaGVyZSBhcmUgZmFpcmx5IHN1YnN0YW50aWFsIGNoYW5nZXMgaW4gYWJ1bmRhbmNlcyBhY3Jvc3MgYnJhaW4gcmVnaW9ucywgbW9zdCBub3RhYmx5IGluIFB2YWxiIGludGVybmV1cm9ucywgd2hpY2ggYXJlIG1vcmUgcHJvbWluZW50IGluIGZyb250YWwgdGhhbiB0ZW1wb3JhbCBjb3J0ZXguICBEb2VzIG91ciBhYnVuZGFuY2UgcmVzdWx0IHdpdGggcmVzcGVjdCB0byBkaXNlYXNlIGhvbGQgd2hlbiBvbmx5IGNvbnNpZGVyaW5nIE1URyB0aXNzdWU/ICAKICAKYGBge3IgY2VsbCB0eXBlIGFidW5kYW5jZXMgbm9ybWFsaXplZCBieSBkaXNlYXNlIGluIE1URywgZmlnLmhlaWdodD04LGZpZy53aWR0aD0xMH0KIyBFeGNsdWRlIHR3byBkb25vcnMgd2l0aCBib3RoIGNvbmRpdGlvbnMKa3BfZG9ub3JzMiA8LSBpbnRlcnNlY3Qoa3BfZG9ub3JzLHJvd25hbWVzKGRvbm9yX21ldGFkYXRhKVtkb25vcl9tZXRhZGF0YSRST0k9PSJNVEciXSkKYWxsR3JvdXAyICA8LSBmYWN0b3IoZG9ub3JfbWV0YWRhdGFba3BfZG9ub3JzMiwiRGlzZWFzZSJdLGxldmVscyA9IGMoIkVwaWxlcHN5IiwiVHVtb3IiKSkKCiMgTWFrZSBwbG90cwpwbHQgPC0gYXMuZ2dwbG90KGZ1bmN0aW9uKCl7CnBhcihtZnJvdz1jKDQsNikpCnBhcihtYXI9YygyLDQsMiwxKSkKZm9yIChzIGluIHN1YmNsYXNzZXMpCiAgdmVyYm9zZUJhcnBsb3QoY2VsbENvdW50c05ba3BfZG9ub3JzMixzXSxhbGxHcm91cDIsbWFpbj0iIix5bGFiPXMscHQuY29sPXN1YmNsYXNzX2NvbG9yc1tzXSwKICAgICAgICAgICAgICAgICB4bGFiPSIiLHlsaW09YygwLG1heChjZWxsQ291bnRzTltrcF9kb25vcnMyLHNdKSoxLjAyKSxjb2w9IndoaXRlIiwKICAgICAgICAgICAgICAgICBhZGRTY2F0dGVycGxvdCA9IFRSVUUsIHBjaD0xOSwgcHQuY2V4PTEsIGNleC5sYWI9MSwgY2V4LmF4aXMgPSAwLjcsIGNleC5tYWluID0gMSkKfSkKcGx0Cmdnc2F2ZSgiYWJ1bmRhbmNlX2JhcnBsb3RzX2J5X2Rpc2Vhc2UucGRmIiwgcGxvdD1wbHQsIGhlaWdodCA9IDgsIHdpZHRoID0gMTApCmBgYAogIAogIApNdWNoIG9mIHRoZSBkaWZmZXJlbmNlIHNlZW1zIHRvIGJlIGV4cGxhaW5lZCBieSByZWdpb24uICBMZXQncyBleHBsaWNpdGx5IHRlc3QgdGhpcyB1c2luZyBhIGxpbmVhciBtb2RlbC4gIAogIApgYGB7ciBsaW5lYXIgbW9kZWwgYWJ1bmRhbmNlc30Ka3BfZG9ub3JzMyA8LSBpbnRlcnNlY3Qoa3BfZG9ub3JzLGludGVyc2VjdChrcF9kb25vcnMscm93bmFtZXMoZG9ub3JfbWV0YWRhdGEpW2Rvbm9yX21ldGFkYXRhJFJPSSE9IlBDeCJdKSkKCmxtX3B2YWxfYXBwbHkgPC0gZnVuY3Rpb24oeCl7CiAgZGF0ICAgPC0gZGF0YS5mcmFtZShkb25vcl9tZXRhZGF0YVtrcF9kb25vcnMzLF0sQWJ1bmRhbmNlID0geCkKICBsbU91dCA8LSBsbShBYnVuZGFuY2V+RGlzZWFzZStST0krU2V4K1Rpc3N1ZS5Tb3VyY2UrQWdlLCBkYXRhID0gZGF0KQogIHN1bW1hcnkobG1PdXQpJGNvZWZmaWNpZW50c1syOmRpbShzdW1tYXJ5KGxtT3V0KSRjb2VmZmljaWVudHMpWzFdLDRdCn0KCmNuIDwtIGMoIkRpc2Vhc2UiLCJST0kiLCJTZXgiLCJUaXNzdWUuU291cmNlIiwiQWdlIikKbG1fcHZhbCA8LSBhcHBseShjZWxsQ291bnRzTltrcF9kb25vcnMzLF0sMixsbV9wdmFsX2FwcGx5KQpkYXRhLmZyYW1lKHQobG1fcHZhbCkpCnJvd25hbWVzKGxtX3B2YWwpIDwtIGNuCmxtX3B2YWxfYWRqdXN0IDwtIGFwcGx5KGxtX3B2YWwsMixwLmFkanVzdCkgICMgRkRSIGNvcnJlY3RlZApkYXRhLmZyYW1lKHQobG1fcHZhbF9hZGp1c3QpKQpgYGAKICAKICAKQWZ0ZXIgYWNjb3VudGluZyBmb3IgU2V4IGFuZCBST0ksIFB2YWxiIGludGVybmV1cm9ucyBzaG93IGRlY3JlYXNlZCBhYnVuZGFuY2UgaW4gZXBpbGVwc3kgY2FzZXMgYW5kIEw1IEVUIG5ldXJvbnMgc2hvdyBkZWNyZWFzZWQgYWJ1bmRhbmNlIGluIHR1bW9yIGNhc2VzLiAgU2V2ZXJhbCBjZWxsIHR5cGVzIHNob3cgYWJ1bmRhbmNlIGRpZmZlcmVuY2Ugd2l0aCByZXNwZWN0IHRvIFJPSSAoaW4gcHJpbmNpcGxlLCB3ZSBjb3VsZCBtYXRjaCB0aGlzIHdpdGggdGhlIGNyb3NzLWFyZWFsIHN0dWR5KS4gIAogIApQbG90IHRoZXNlLiAgCiAgCmBgYHtyIHBsb3Qgc2lnbmlmaWNhbnQgdHlwZXMsZmlnLmhlaWdodD00LGZpZy53aWR0aD03fQphbGxHcm91cHNEUiA8LSBwYXN0ZShkb25vcl9tZXRhZGF0YVtrcF9kb25vcnMzLCJST0kiXSxzdWJzdHIoZG9ub3JfbWV0YWRhdGFba3BfZG9ub3JzMywiRGlzZWFzZSJdLDEsMiksc2VwPSI6IikKCiMgTWFrZSBwbG90cwpwbHQgPC0gYXMuZ2dwbG90KGZ1bmN0aW9uKCl7CnBhcihtZnJvdz1jKDEsNCkpCnBhcihtYXI9YygxMCwzLDcsMSkpCmZvciAocyBpbiBjKCJQdmFsYiIsICJMNSBFVCIsICJMNCBJVCIsICJMNmIiKSkKICB2ZXJib3NlQmFycGxvdChjZWxsQ291bnRzTltrcF9kb25vcnMzLHNdLGFsbEdyb3Vwc0RSLG1haW49cyx5bGFiPSIiLEtydXNrYWxUZXN0ID0gRkFMU0UsY29sPSJ3aGl0ZSIsCiAgICAgICAgICAgICAgICAgeGxhYj0iIix5bGltPWMoMCxtYXgoY2VsbENvdW50c05ba3BfZG9ub3JzMyxzXSkqMS4xKSwgbGFzPTIsIHB0LmNvbD1zdWJjbGFzc19jb2xvcnNbc10sCiAgICAgICAgICAgICAgICAgYWRkU2NhdHRlcnBsb3QgPSBUUlVFLCBwY2g9MTksIHB0LmNleD0xLCBjZXgubGFiPTEuNSwgY2V4LmF4aXMgPSAwLjcsIGNleC5tYWluID0gMS41KQp9KQpwbHQKZ2dzYXZlKCJzZWxlY3RfYWJ1bmRhbmNlX2JhcnBsb3RzX2J5X2Rpc2Vhc2VfYW5kX3JlZ2lvbi5wZGYiLCBwbG90PXBsdCwgaGVpZ2h0ID0gNCwgd2lkdGggPSA3KQpgYGAKICAKICAKRm9yIGNvbXBsZXRlbmVzcywgbGV0J3MgdHJ5IHRoZSBzYW1lIHRoaW5nIHdpdGggZ2VuZXMgZXhwcmVzc2VkLiAgCiAgCmBgYHtyIGxpbmVhciBtb2RlbCBnZW5lc30KbG1fcHZhbEcgPC0gYXBwbHkoZ2VuZUNvdW50c1trcF9kb25vcnMzLF0sMixsbV9wdmFsX2FwcGx5KQpkYXRhLmZyYW1lKHQobG1fcHZhbEcpKQpyb3duYW1lcyhsbV9wdmFsRykgPC0gY24KbG1fcHZhbF9hZGp1c3RHIDwtIGFwcGx5KGxtX3B2YWxHLDIscC5hZGp1c3QpICAjIEZEUiBjb3JyZWN0ZWQKZGF0YS5mcmFtZSh0KGxtX3B2YWxfYWRqdXN0RykpCmBgYAogIApObyBzaWduaWZpY2FudCBkaWZmZXJlbmNlcy4gIAogIAogIAojIyMgQWJ1bmRhbmNlcyBhbmQgZ2VuZSBjb3VudHMgYnkgY2xhc3MgIAogIApGb3IgYWJ1bmRhbmNlcyBsZXQncyBsb29rIGF0IEUvSSByYXRpb3MsIHNpbmNlIHRoZSBmcmFjdGlvbiBvZiBub24tbmV1cm9uYWwgY2VsbHMgd2FzIHNldCBiYXNlZCBvbiBGQUNTLiAgCiAgCmBgYHtyIGFidW5kYW5jZSBieSBjbGFzcyxmaWcuaGVpZ2h0PTUsZmlnLndpZHRoPTh9CkVJIDwtIE5VTEwKZm9yIChsIGluIGh2c19kb25vcnMpCiAgRUkgPC0gYyhFSSxzdW0oY2VsbENvdW50c1tsLGNsYXNzR3JvdXBzW1syXV1dKS9zdW0oY2VsbENvdW50c1tsLGNsYXNzR3JvdXBzW1sxXV1dKSkKbmFtZXMoRUkpIDwtIGh2c19kb25vcnMKCmRhdCAgIDwtIGRhdGEuZnJhbWUoZG9ub3JfbWV0YWRhdGFba3BfZG9ub3JzMyxjbl0sQWJ1bmRhbmNlID0gRUlba3BfZG9ub3JzM10pCmxtT3V0IDwtIGxtKEFidW5kYW5jZX5EaXNlYXNlK1JPSStTZXgsIGRhdGEgPSBkYXQpIApzdW1tYXJ5KGxtT3V0KSRjb2VmZmljaWVudHMKCiMgTWFrZSBwbG90cwpwbHQgPC0gYXMuZ2dwbG90KGZ1bmN0aW9uKCl7CnBhcihtZnJvdz1jKDEsMikpCnBhcihtYXI9Yyg4LDQsOCwxKSkKdmVyYm9zZUJhcnBsb3QoRUlba3BfZG9ub3JzM10sYWxsR3JvdXBzRFIsbWFpbj0iIix5bGFiPSJFL0kgcmF0aW8iLCB4bGFiPSIiLHlsaW09YygwLDUpLAogICAgICAgICAgICAgICBhZGRTY2F0dGVycGxvdCA9IFRSVUUsIHBjaD0xOSwgcHQuY2V4PTEsIGNleC5sYWI9MSwgY2V4LmF4aXMgPSAxLCAKICAgICAgICAgICAgICAgY2V4Lm1haW4gPSAxLCBwdC5jb2wgPSAiYmxhY2siLCBjb2w9IndoaXRlIikKc2V0LnNlZWQoMykKcGxvdCh4PWppdHRlcihyZXAoMCxsZW5ndGgoRUkpKSksIEVJLCBtYWluPSIiLHlsYWI9IkUvSSByYXRpbyIseWxpbT1jKDAsNSkscGNoPTE5LCAKICAgICB4bGFiPSIiLGNleC5sYWI9MSwgeGxpbT1jKC0wLjAyMiwwLjMpKQphYmxpbmUoaD0wKQp9KQpwbHQKZ2dzYXZlKCJFSV9yYXRpb19wbG90cy5wZGYiLCBwbG90PXBsdCwgaGVpZ2h0ID0gNiwgd2lkdGggPSA4KQpgYGAKCk5vIGRpZmZlcmVuY2UgaW4gRS9JIHJhdGlvIHdpdGggcmVzcGVjdCB0byB0aGVzZSBkb25vciBtZXRhZGF0YS4gCiAgCkZvciBnZW5lcyBleHByZXNzZWQsIHdlJ2xsIGRlZmluZSBhIGdlbmUgYXMgZXhwcmVzc2VkIGluIGEgY2xhc3MgaWYgaXQgaXMgZXhwcmVzc2VkIGluIGF0IGxlYXN0IG9uZSBzdWJjbGFzcyAgCiAgCmBgYHtyIGdlbmUgZXhwcmVzc2lvbiBieSBjbGFzcyxmaWcuaGVpZ2h0PTQsZmlnLndpZHRoPTl9CnBsdCA8LSBhcy5nZ3Bsb3QoZnVuY3Rpb24oKXsKcGFyKG1mcm93PWMoMSwzKSkKZ2V4TCA8LSBsaXN0KCkKZm9yIChjbCBpbiBuYW1lcyhjbGFzc0dyb3VwcykpewogIHN1YnMgPC0gY2xhc3NHcm91cHNbW2NsXV0KICB0bWUgIDwtIHRtZWFuRXhwcltbc3Vic1sxXV1dWyxkb25vcnNdCiAgdG1lW2lzLm5hKHRtZSldIDwtIDAKICBmb3IgKHMgaW4gc3VicykgewogICAgdG1lMiA8LSB0bWVhbkV4cHJbW3NdXVssZG9ub3JzXQogICAgdG1lMltpcy5uYSh0bWUyKV0gPC0gMAogICAgdG1lICA8LSB0bWUrdG1lMgogIH0KICBnZXggPC0gZ2V4TFtbY2xdXSA8LSBhcy5udW1lcmljKGNvbFN1bXModG1lPjApKQogIAogICMgTWFrZSBwbG90cwogIHBhcihtYXI9Yyg4LDQsNiwxKSkKICBrcCA8LSBtYXRjaChrcF9kb25vcnMzLGRvbm9ycykKICB2ZXJib3NlQmFycGxvdChnZXhba3BdLzEwMDAsYWxsR3JvdXBzRFIsbWFpbj0iIix5bGFiPXBhc3RlKGNsLCIodGhvdXNhbmRzKSIpLHhsYWI9IiIseWxpbT1jKDAsMTgpLAogICAgICAgICAgICAgICAgIGFkZFNjYXR0ZXJwbG90ID0gVFJVRSwgcGNoPTE5LCBwdC5jZXg9MSwgY2V4LmxhYj0xLCBjZXguYXhpcyA9IDEsIGNleC5tYWluID0gMSwKICAgICAgICAgICAgICAgICBjb2w9IndoaXRlIixwdC5jb2w9dHlwZV9pbmZvJGNsYXNzX2NvbG9yW2dyZXAoY2wsdHlwZV9pbmZvJGNsYXNzX2xhYmVsKVsxXV0pCn0KfSkKcGx0Cmdnc2F2ZSgiZ2VuZUNvdW50X2J5X2NsYXNzX2JhcnBsb3RzLnBkZiIsIHBsb3Q9cGx0LCBoZWlnaHQgPSA2LCB3aWR0aCA9IDgpCmBgYAogIApObyBnZW5lIGRpZmZlcmVuY2VzIGF0IHRoZSBjbGFzcyBsZXZlbCBlaXRoZXIuICAKICAKICAKIyMgTmV1cm9zdXJnaWNhbCB2cy4gcG9zdG1vcnRlbSBjb21wYXJpc29ucwogIApJbiB0aGlzIHNlY3Rpb24gd2UnbGwgYXNzZXNzIHN1YmNsYXNzIHZhcmlhdGlvbiBpbiBuZXVyb3N1cmdpY2FsIHRpc3N1ZSBhbmQgY29tcGFyZSBpdCB0byByZWZlcmVuY2UgZGF0YS4uLiBmb3Igd2hpY2ggdGlzc3VlIHNvdXJjZSBpcyBpdCBoaWdoZXI/ICBIb3cgZG9lcyBpdCBkaWZmZXIgYnkgc3ViY2xhc3M/ICBGb3IgdGhpcyBhbmFseXNpcyB3ZSBsaW1pdCBvdXJzZWx2ZXMgdG8gdGhlIDQ1IEhWUyBkb25vcnMgd2l0aCBlcGlsZXBzeSBmcm9tIE1URyB0byBkZWNyZWFzZSB2YXJpYXRpb24gYXNzb2NpYXRlZCB3aXRoIGRvbm9yIG1ldGFkYXRhIGFuZCB0cnkgdG8gZm9jdXMgbW9zdGx5IG9uIG5ldXJvc3VyZ2ljYWwgdnMuIHBvc3Rtb3J0ZW0gKHdoaWNoIGlzIGFsc28gTVRHKS4gIFdlIHdpbGwgdXNlIHR3byBtZXRob2RzIGZvciBkZWZpbmluZyB2YXJpYXRpb246ICgxKSBjb2VmZmljaWVudCBvZiB2YXJpYXRpb24gKHNkL21lYW4pIGFuZCAoMikgaW50ZXJxdWFydGlsZSByYW5nZSAvIG1lZGlhbi4gIEluIGJvdGggY2FzZXMgdGhlIGlkZWEgaXMgdG8gZGVmaW5lIHRoZSBzcHJlYWQgb2YgdGhlIGRpc3RyaWJ1dGlvbnMuICBOb3RlIHRoYXQgd2Ugb25seSBoYXZlIHRocmVlIGRvbm9ycyB3aXRoIGNlbGxzIGNvbGxlY3RlZCBmcm9tIGFsbCBsYXllcnMgdXNpbmcgdGhlIHNhbWUgZXhwZXJpbWVudGFsIHN0cmF0ZWd5LCBIMTguMzAuMDAyLCBIMTkuMzAuMDAxIGFuZCBIMTkuMzAuMDAyICwgYW5kIHNvIHdlIHdpbGwgb25seSBsb29rIGF0IHRoZXNlIHRocmVlIGZvciBhYnVuZGFuY2UgYXNzZXNzbWVudHMKICAKYGBge3IgZGVmaW5lIHBlciBzdWJjbGFzcyB2YXJpYW5jZX0KY29udHJvbF9kb25vcnMyIDwtIGMoIkgxOC4zMC4wMDIiLCJIMTkuMzAuMDAxIiwiSDE5LjMwLjAwMiIpCm5zX2NvdW50cyAgPC0gY2VsbENvdW50c05baHZzX2Rvbm9ycyxdWyhkb25vcl9tZXRhZGF0YVtodnNfZG9ub3JzLF0kUk9JPT0iTVRHIikmCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIChkb25vcl9tZXRhZGF0YVtodnNfZG9ub3JzLF0kRGlzZWFzZT09IkVwaWxlcHN5IiksXQptdGdfZXBfZG9ub3JzIDwtIHJvd25hbWVzKG5zX2NvdW50cykKcG1fY291bnRzICA8LSBjZWxsQ291bnRzTltjb250cm9sX2Rvbm9yczIsXQoKbnNfbWVhbiA8LSBhcHBseShuc19jb3VudHMsMixtZWFuLG5hLnJtPVRSVUUpCnBtX21lYW4gPC0gYXBwbHkocG1fY291bnRzLDIsbWVhbixuYS5ybT1UUlVFKQpuc19DT1YgIDwtIGFwcGx5KG5zX2NvdW50cywyLHNkLG5hLnJtPVRSVUUpL25zX21lYW4KcG1fQ09WICA8LSBhcHBseShwbV9jb3VudHMsMixzZCxuYS5ybT1UUlVFKS9wbV9tZWFuCm5zX0lRUiAgPC0gYXBwbHkobnNfY291bnRzLDIsSVFSLG5hLnJtPVRSVUUpL2FwcGx5KG5zX2NvdW50cywyLG1lZGlhbixuYS5ybT1UUlVFKQpwbV9JUVIgIDwtIGFwcGx5KHBtX2NvdW50cywyLElRUixuYS5ybT1UUlVFKS9hcHBseShwbV9jb3VudHMsMixtZWRpYW4sbmEucm09VFJVRSkKYGBgCiAgCiAgCk5vdyBsZXQncyBwbG90IHRoZSByZXN1bHRzLiAgCiAgCmBgYHtyIHBsb3QgdmFyaWF0aW9uIGNvbXBhcmlzb24sIGZpZy5oZWlnaHQ9MTIsZmlnLndpZHRoPTEwfQojIE5PVEU6IEkgbWlnaHQgbmVlZCB0byBtYW51YWxseSBhZGp1c3QgdGhlIHhsaW0gYW5kIHlsaW0gdmFsdWVzIGJlbG93CnBsb3RfdG1wIDwtIGZ1bmN0aW9uKHgseSxzLHRleHQuY29sPSJibGFjayIseGxhYj0iUG9zdCBtb3J0ZW0gY29udHJvbHMiLHlsYWI9Ik5ldXJvc3VyZ2ljYWwgKE1URywgZXBpbGVwc3kpIiwgeHlsaW5lPVRSVUUsIC4uLil7CiAgdmVyYm9zZVNjYXR0ZXJwbG90KHgseSxjb2w9IndoaXRlIix4bGFiPXhsYWIseWxhYj15bGFiLC4uLikKICBpZih4eWxpbmUpIGFibGluZSgwLDEsY29sPSJncmV5IixsdHk9ImRhc2hlZCIpCiAgdGV4dCh4LHkscyxjb2w9dGV4dC5jb2wpCn0KCnBsdCA8LSBhcy5nZ3Bsb3QoZnVuY3Rpb24oKXsKcGFyKG1mcm93PWMoMiwyKSkKcGxvdF90bXAocG1fbWVhbixuc19tZWFuLHN1YmNsYXNzZXMsc3ViY2xhc3NfY29sb3JzLG1haW49Im1lYW4iLGxvZz0ieHkiLHhsaW09YygwLjAwMjUsMC42KSx5bGltPWMoMC4wMDI1LDAuNikpCnBsb3RfdG1wKHBtX0NPVixuc19DT1Ysc3ViY2xhc3NlcyxzdWJjbGFzc19jb2xvcnMsbWFpbj0iQ09WIixsb2c9Inh5Iix4bGltPWMoMC4wNSwyLjUpLHlsaW09YygwLjA1LDIuNSkpCnBsb3RfdG1wKHBtX0lRUixuc19JUVIsc3ViY2xhc3NlcyxzdWJjbGFzc19jb2xvcnMsbWFpbj0iSVFSL21lZGlhbiIsbG9nPSJ4eSIseGxpbT1jKDAuMDUsNikseWxpbT1jKDAuMDUsNikpCnBsb3RfdG1wKG5zX21lYW4sbnNfQ09WLHN1YmNsYXNzZXMsc3ViY2xhc3NfY29sb3JzLG1haW49IlBNIix4bGFiPSJNZWFuIGFidW5kYW5jZSAoTlMpIiwKICAgICAgICAgeWxhYj0iQ09WIGFidW5kYW5jZSAoTlMpIix4bGltPWMoLTAuMDUsMC42KSx5bGltPWMoMCwyKSwgeHlsaW5lPUZBTFNFKQphYmxpbmUodj0wKTsgYWJsaW5lKGg9MCkKfSkKcGx0Cmdnc2F2ZSgidmFyaWFuY2VfY29tcGFyaXNvbl9OU3ZzUE0ucGRmIiwgcGxvdD1wbHQsIGhlaWdodCA9IDEyLCB3aWR0aCA9IDEwKQpgYGAKICAKT3ZlcmFsbCwgZ29vZCBhZ3JlZW1lbnQgaW4gTlMgdnMuIFBNLCBlc3BlY2lhbGx5IGZvciBtZWFuIGV4cHJlc3Npb24uIEw2IElUIENhcjMgaXMgYW4gb3V0bGllciBvbiB2YXJpYW5jZSwgbGlrZWx5IHNpbmNlIHRoZXJlIGFyZSBvbmx5IDMgY29udHJvbCBkb25vcnMuIEkgZG9u4oCZdCB0aGluayB3ZSBjYW4gcmVhbGx5IHRydXN0IHZhcmlhbmNlIGNhbGxzIGluIG91ciBOUyBkYXRhIGluIGdlbmVyYWwsIGVzcGVjaWFsbHkgc2luY2Ugd2UgZG9u4oCZdCBmaW5kIGFueSBjb3JyZWxhdGlvbiBiZXR3ZWVuIE5TIGFuZCBQTSB3aGVuIHdlIGluY2x1ZGUgdGhlIHJlbWFpbmluZyB0d28gZG9ub3JzIGZvciBuZXVyb25zIG9ubHkuIEludGVyZXN0aW5nbHksIHJlbGF0aXZlIHRvIHRoZWlyIG1lYW4gYWJ1bmRhbmNlcywgbm9uLW5ldXJvbnMgaGF2ZSBoaWdoZXIgQ09WIHRoYW4gbmV1cm9ucy4gIAogIApOb3cgbGV0J3MgdmlzdWFsaXplIHRoaXMgYnkgc3ViY2xhc3MuICAKICAKYGBge3IgcG9zdG1vcnRlbSB2cyBuZXVyb3N1cmdpY2FsIGFidW5kYW5jZSwgZmlnLmhlaWdodD04LGZpZy53aWR0aD0xMH0KY291bnRzTUUgPC0gcmJpbmQocG1fY291bnRzLG5zX2NvdW50cykKCmNhdCA8LSBmYWN0b3IoYyhyZXAoIlBNIixkaW0ocG1fY291bnRzKVsxXSkscmVwKCJOUyIsZGltKG5zX2NvdW50cylbMV0pKSxsZXZlbHM9YygiUE0iLCJOUyIpKQpwbHQgPC0gYXMuZ2dwbG90KGZ1bmN0aW9uKCl7CnBhcihtZnJvdz1jKDQsNikpCnBhcihtYXI9YygyLDUsMiwwKSkKZm9yIChzIGluIHN1YmNsYXNzZXMpCiAgdmVyYm9zZUJhcnBsb3QoY291bnRzTUVbLHNdLGNhdCxtYWluPSIiLHlsYWI9cyx4bGFiPSIiLCAKICAgICAgICAgICAgICAgICB5bGltID0gYygwLG1heChjb3VudHNNRVssc10pKSxjb2w9IndoaXRlIixwdC5jb2wgPSBzdWJjbGFzc19jb2xvcnNbc10sCiAgICAgICAgICAgICAgICAgYWRkU2NhdHRlcnBsb3QgPSBUUlVFLCBwY2g9MTksIHB0LmNleD0xLCBjZXgubGFiPTEsIGNleC5heGlzID0gMSwgY2V4Lm1haW4gPSAxKQp9KQpwbHQKZ2dzYXZlKCJhYnVuZGFuY2VfYmFycGxvdHNfYnlfTlN2c1BNLnBkZiIsIHBsb3Q9cGx0LCBoZWlnaHQgPSA4LCB3aWR0aCA9IDEwKQpgYGAKICAKTm93IGxldCdzIHJldmlzaXQgZ2VuZSBleHByZXNzaW9uIGNoYW5nZXMuICBXZSBjYW4gY29tcGFyZSBnZW5lIGV4cHJlc3Npb24gZGlmZmVyZW5jZXMgYW5kIHNlZSBob3cgdGhlc2UgcmVsYXRlIHRvIHdoYXQgd2UgcHVibGlzaGVkIGluIEhvZGdlIGV0IGFsIDIwMTkgKGZvciBMNSBvbmx5KS4gIExldCdzIGZpcnN0IHBsb3QgdGhlIG51bWJlciBvZiBnZW5lcyBleHByZXNzZWQgcGVyIGNsYXNzIGluIHBvc3Rtb3J0ZW0gdnMuIG5ldXJvc3VyZ2ljYWwsIGNvbnNpZGVyaW5nIG9uIE1URyBkb25vcnMuICAKICAKYGBge3IgcG9zdG1vcnRlbSB2cyBuZXVyb3N1cmdpY2FsIGdlbmVzLCBmaWcuaGVpZ2h0PTgsZmlnLndpZHRoPTEwfQpnZW5lQ291bnRzTUUgPC0gZ2VuZUNvdW50c1tjKGNvbnRyb2xfZG9ub3JzMixtdGdfZXBfZG9ub3JzKSxdCgpjYXQgPC0gZmFjdG9yKGMocmVwKCJQTSIsbGVuZ3RoKGNvbnRyb2xfZG9ub3JzMikpLHJlcCgiTlMiLGxlbmd0aChtdGdfZXBfZG9ub3JzKSkpLGxldmVscz1jKCJQTSIsIk5TIikpCnBsdCA8LSBhcy5nZ3Bsb3QoZnVuY3Rpb24oKXsKcGFyKG1mcm93PWMoNCw2KSkKcGFyKG1hcj1jKDIsNSwyLDApKQpmb3IgKHMgaW4gc3ViY2xhc3NlcykKICB2ZXJib3NlQmFycGxvdChnZW5lQ291bnRzTUVbLHNdLzEwMDAsY2F0LG1haW49IiIseWxhYj1wYXN0ZShzLCIodGhvdXNhbmRzKSIpLHhsYWI9IiIsIAogICAgICAgICAgICAgICAgIHlsaW0gPSBjKDAsMTUpLGNvbD0id2hpdGUiLHB0LmNvbCA9IHN1YmNsYXNzX2NvbG9yc1tzXSwKICAgICAgICAgICAgICAgICBhZGRTY2F0dGVycGxvdCA9IFRSVUUsIHBjaD0xOSwgcHQuY2V4PTEsIGNleC5sYWI9MSwgY2V4LmF4aXMgPSAxLCBjZXgubWFpbiA9IDEpCn0pCnBsdApnZ3NhdmUoImdlbmVDb3VudHNfYmFycGxvdHNfYnlfTlN2c1BNLnBkZiIsIHBsb3Q9cGx0LCBoZWlnaHQgPSA4LCB3aWR0aCA9IDEwKQpgYGAKICAKU29tZSBnbGlhbCBjZWxsIHR5cGVzIGhhdmUgbW9yZSBnZW5lcyBleHByZXNzZWQgaW4gbmV1cm9zdXJnaWNhbCB0aGFuIHBvc3Rtb3J0ZW0gY2FzZXMsIGFsdGhvdWdoIEkgZXhwZWN0IHRoaXMgaXMgcHJvYmFibHkgYW4gaXNzdWUgd2l0aCBRQywgcmF0aGVyIHRoYW4gYSByZWFsIHJlc3VsdC4gIE90aGVyd2lzZSwgdGhlcmUgYXJlIGNvbXBhcmFibGUgZ2VuZXMgZXhwcmVzc2VkIGluIHRoZSByZWZlcmVuY2UgYW5kIGluIG5ldXJvc3VyZ2ljYWwgdGlzc3VlLiAgTGV0J3MgcGxvdCB0aGlzIG1vcmUgY29tcGFjdGx5IGZvciBpbmNsdXNpb24gaW4gYSBzdXBwbGVtZW50YWwgZmlndXJlLiAgCiAgCmBgYHtyIGdlbmVzIGluIFBNIHZzLiBOUywgZmlnLmhlaWdodD01LGZpZy53aWR0aD0xMX0KZGYgICAgID0gZGF0YS5mcmFtZShYPWNvbE1lYW5zKGdlbmVDb3VudHNNRVtjYXQ9PSJQTSIsXSxuYS5ybT1UUlVFKSxlcnJYPWFwcGx5KGdlbmVDb3VudHNNRVtjYXQ9PSJQTSIsXSwyLHNkLG5hLnJtPVRSVUUpLAogICAgICAgICAgICAgICAgICAgIFk9Y29sTWVhbnMoZ2VuZUNvdW50c01FW2NhdD09Ik5TIixdLG5hLnJtPVRSVUUpLGVyclk9YXBwbHkoZ2VuZUNvdW50c01FW2NhdD09Ik5TIixdLDIsc2QsbmEucm09VFJVRSkpLzEwMDAKcmFuZ2VYID0gcmFuZ2UoYyhkZiRYICsgZGYkZXJyWCwgZGYkWCAtIGRmJGVyclgpKQpyYW5nZVkgPSByYW5nZShjKGRmJFkgKyBkZiRlcnJZLCBkZiRZIC0gZGYkZXJyWSkpCgpwbHQgPC0gYXMuZ2dwbG90KGZ1bmN0aW9uKCl7CnBhcihtZnJvdz1jKDEsMikpCnBsb3QoZGYkWCwgZGYkWSwgeGxpbSA9IHJhbmdlWCwgeWxpbSA9IHJhbmdlWSx4bGFiPSJHZW5lcyBkZXRlY3RlZCAodGhvdXNhbmRzLCBwb3N0bW9ydGVtKSIsCiAgICAgeWxhYj0iR2VuZXMgZGV0ZWN0ZWQgKHRob3VzYW5kcywgbmV1cm9zdXJnaWNhbCkiLHBjaD0xOSxjZXg9Mixjb2w9c3ViY2xhc3NfY29sb3JzKQpzZWdtZW50cyhkZiRYLCBkZiRZIC0gZGYkZXJyWSwgZGYkWCwgZGYkWSArIGRmJGVyclksY29sPXN1YmNsYXNzX2NvbG9ycykKc2VnbWVudHMoZGYkWCAtIGRmJGVyclgsIGRmJFksIGRmJFggKyBkZiRlcnJYLCBkZiRZLGNvbD1zdWJjbGFzc19jb2xvcnMpCmFibGluZSgwLDEsY29sPSJncmV5IikKCnZlcmJvc2VTY2F0dGVycGxvdChkZiRYLCBkZiRZLCB4bGltID0gcmFuZ2VYLCB5bGltID0gcmFuZ2VZLHhsYWI9IkdlbmVzIGRldGVjdGVkICh0aG91c2FuZHMsIHBvc3Rtb3J0ZW0pIiwKICAgICB5bGFiPSJHZW5lcyBkZXRlY3RlZCAodGhvdXNhbmRzLCBuZXVyb3N1cmdpY2FsKSIscGNoPTE5LGNleD0wLjI1LGNvbD1zdWJjbGFzc19jb2xvcnMpCnNlZ21lbnRzKGRmJFgsIGRmJFkgLSBkZiRlcnJZLCBkZiRYLCBkZiRZICsgZGYkZXJyWSxjb2w9c3ViY2xhc3NfY29sb3JzKQpzZWdtZW50cyhkZiRYIC0gZGYkZXJyWCwgZGYkWSwgZGYkWCArIGRmJGVyclgsIGRmJFksY29sPXN1YmNsYXNzX2NvbG9ycykKYWJsaW5lKDAsMSxjb2w9ImdyZXkiKQp0ZXh0KGRmJFgsIGRmJFksIHN1YmNsYXNzZXMsY29sPXN1YmNsYXNzX2NvbG9ycykKfSkKcGx0Cmdnc2F2ZSgiZ2VuZUNvdW50c19tZWFuc19zY2F0dGVycGxvdHNfYnlfTlN2c1BNLnBkZiIsIHBsb3Q9cGx0LCBoZWlnaHQgPSA1LCB3aWR0aCA9IDExKQpgYGAKICAKQ29tcGFyZSB0aGUgU0RzIGluIG5ldXJvc3VyZ2ljYWwgdnMuIHBvc3Rtb3J0ZW0gdGlzc3Vlcy4gIAogIApgYGB7ciBTRCBnZW5lcyBkZXRlY3RlZCxmaWcuaGVpZ2h0PTUsZmlnLndpZHRoPTIuNX0KeCA9IGMoZGYkZXJyWCxkZiRlcnJZKQpnID0gYyhyZXAoIlBNIixsZW5ndGgoc3ViY2xhc3NlcykpLHJlcCgiTlMiLGxlbmd0aChzdWJjbGFzc2VzKSkpCnZlcmJvc2VCYXJwbG90KHgsZyx4bGFiPSIiLHlsYWI9IlNEIGdlbmVzIGRldGVjdGVkICh0aG91c2FuZHMpIixjb2w9IndoaXRlIikKYGBgCiAgCiAgCkxldCdzIGxvb2sgc3BlY2lmaWNhbGx5IGF0IHdoaWNoIGdlbmVzIGFyZSBtb3JlIGhpZ2hseSBleHByZXNzZWQgYnkgc291cmNlIGZvciBQdmFsYiwgTDUgRVQsIGFuZCBMMi8zIElUIHR5cGVzICh0aGUgdHdvIHR5cGVzIHdpdGggYWJ1bmRhbmNlIGNoYW5nZXMgYW5kIGEgdHlwZSB3aXRoIG1hbnkgbnVjbGVpIGFuZCBsYXJnZSB2YXJpYXRpb24pLCBob3BlZnVsbHkgc29tZXdoYXQgcmVwcmVzZW50YXRpdmUgb2YgYWxsIHR5cGVzLiAgCiAgCmBgYHtyIGdlbmUgY29tcGFyaXNvbiBzY2F0dGVycGxvdCwgZmlnLmhlaWdodD00LGZpZy53aWR0aD0xOH0KbWVkaWFuX2V4cHJlc3Npb25fQ1QgPC0gbWVkaWFuX2V4cHJlc3Npb25fTlMgPC0gbGlzdCgpCmZvciAocyBpbiBzdWJjbGFzc2VzKXsKICBjdF90bXAgPC0gdG1lYW5FeHByW1tzXV1bLG10Z19lcF9kb25vcnNdCiAgY3RfdG1wW2N0X3RtcD09MF0gPC0gTkEKICBtZWRpYW5fZXhwcmVzc2lvbl9DVFtbc11dIDwtIGFwcGx5KGN0X3RtcCwxLG1lZGlhbixuYS5ybT1UUlVFKQogIHBtX3RtcCA8LSB0bWVhbkV4cHJbW3NdXVssY29udHJvbF9kb25vcnNdCiAgcG1fdG1wW3BtX3RtcD09MF0gPC0gTkEKICBtZWRpYW5fZXhwcmVzc2lvbl9OU1tbc11dIDwtIGFwcGx5KHBtX3RtcCwxLG1lZGlhbixuYS5ybT1UUlVFKQp9CgpwbHQgPC0gYXMuZ2dwbG90KGZ1bmN0aW9uKCl7CnBhcihtZnJvdz1jKDEsNCkpCnBhcihtYXI9Yyg4LDQsNSwxKSkKZm9yIChzIGluIGMoIlB2YWxiIiwiTDUgRVQiLCJMMi8zIElUIiwiT2xpZ28iKSl7CiAga3BHbiA8LSBwbWF4KG1lZGlhbl9leHByZXNzaW9uX0NUW1tzXV0sbWVkaWFuX2V4cHJlc3Npb25fTlNbW3NdXSk+MAogIHggICAgPC0gbWVkaWFuX2V4cHJlc3Npb25fQ1RbW3NdXVtrcEduXQogIHkgICAgPC0gbWVkaWFuX2V4cHJlc3Npb25fTlNbW3NdXVtrcEduXQogIHZlcmJvc2VTY2F0dGVycGxvdCh4LHkscGNoPTE5LG1haW49cyx4bGFiPSJNZWRpYW4gbG9nMihDUE0rMSk6IFBNIix5bGFiPSJNZWRpYW4gbG9nMihDUE0rMSk6IE5TIixjZXg9MC41KQogIG5tICAgPC0gbmFtZXMoYyhoZWFkKHNvcnQobWVkaWFuX2V4cHJlc3Npb25fQ1RbW3NdXS1tZWRpYW5fZXhwcmVzc2lvbl9OU1tbc11dKSw1KSwKICAgICAgICAgICAgICAgICAgaGVhZChzb3J0KG1lZGlhbl9leHByZXNzaW9uX05TW1tzXV0tbWVkaWFuX2V4cHJlc3Npb25fQ1RbW3NdXSksNSkpKQogIHRleHQoeFtubV0seVtubV0sbm0sY2V4PTAuNzUpCn0KfSkKcGx0Cmdnc2F2ZSgidG9wX2RleF9nZW5lc19ieV9zdWJjbGFzc19zY2F0dGVycGxvdF9OU3ZzUE0ucGRmIiwgcGxvdD1wbHQsIGhlaWdodCA9IDQsIHdpZHRoID0gMTgpCmBgYAogIApHb29kIGNvcnJlbGF0aW9uIG92ZXJhbGwsIGJ1dCBzb21lIGdlbmVzIG9mZiBkaWFnb25hbCB0aGF0IGNvdWxkIGJlIGZvbGxvd2VkIHVwIGF0IHNvbWUgcG9pbnQuICAKICAKICAKIyMgQXNzZXNzIGNlbGwgdHlwZSB2YXJpYWJpbGl0eQogIApUaGUgc2VjdGlvbiBmb2N1c2VzIG9uIHF1YW50aWZ5aW5nIGNlbGwgdHlwZSB2YXJpYXRpb24uICBXZSB3YW50IHRvIGFkZHJlc3MgdGhlIGZvbGxvd2luZyBxdWVzdGlvbnM6ICAKKiBDYW4gd2UgZGV0ZXJtaW5lIGEgY2VsbCB0eXBlIHZhcmlhdGlvbiBzY29yZSBieSBjb21wYXJpbmcgaW50cmEtZG9ub3IgdnMuIGludGVyLWRvbm9yIHZhcmlhdGlvbiB3aXRoaW4gZWFjaCBjZWxsIHR5cGU/ICAKKiBXaXRoaW4gdHlwZXM6IEhvdyBtdWNoIHZhcmlhdGlvbj8gIFdoaWNoIGdlbmVzIGFyZSBtb3N0IHZhcmlhYmxlPyAgCiogQmV0d2VlbiB0eXBlczogV2hpY2ggdHlwZXMgYXJlIG1vc3QgdmFyaWFibGU/IENvbnNlcnZlZCB2cy4gZGlzdGluY3QgZ2VuZXM/ICAKKiBBc3NvY2lhdGlvbiBiZXR3ZWVuIHZhcmlhdGlvbiBhbmQgc2V4LCBkaXNlYXNlLCBldGMuICAKCldlIHdpbGwgdXNlIHR3byBwcmltYXJ5IHN0cmF0ZWdpZXMgdG8gYWRkcmVzcyB0aGlzIHF1ZXN0aW9uLiAgCjEuIFJhbmRvbSBmb3Jlc3QgcHJlZGljdGlvbjogRm9yIHdoYXQgZnJhY3Rpb24gb2YgY2VsbHMgY2FuIHRoZSBjZWxsIHR5cGUgb2Ygb3JpZ2luIGJlIGFjY3VyYXRlbHkgcHJlZGljdGVkLiAgVGhpcyB3aWxsIGJlIGNhbGN1bGF0ZWQgc2VwYXJhdGVseSBmb3IgZWFjaCBkb25vciwgZm9yIGVhY2ggY2VsbCB0eXBlLCBhbmQgdXNpbmcgdmFyaW91cyBzdWJzZXRzIG9mIHRoZSBvdmVyYWxsIGRhdGEgc2V0IHRvIHJlZHVjZSBiaWFzZXMuICAKMi4gR2VuZSB2YXJpYW5jZSBzY29yZTogRm9yIGVhY2ggZ2VuZSwgaG93IHZhcmlhYmxlIGlzIGV4cHJlc3Npb24gYWNyb3NzIHJhbmRvbSBjZWxscyBmcm9tIHRoZSBzYW1lIHZzLiBkaXN0aW5jdCBkb25vcnM/ICBXaGljaCBnZW5lcyBzaG93IHRoZSBtb3N0IHZhcmlhbmNlIGFmdGVyIGFjY291bnRpbmcgZm9yIGV4cHJlc3Npb24gbGV2ZWxzPyAgSXMgdGhpcyBjb25zZXJ2ZWQgYWNyb3NzIHR5cGVzPyAgV2hpY2ggdHlwZXMgaGF2ZSB0aGUgbW9zdCB2YXJpYW5jZT8gIAogIApCb3RoIG1ldGhvZHMgd2lsbCByZWx5IG9uIGxvZzJDUE0gZGF0YSBmb2xsb3dlZCBieSBzdGFuZGFyZCBTZXVyYXQgcHJvY2Vzc2luZyBhcyBhIGJhc2VsaW5lIGZvciBhbmFseXNpcywgd2hpY2ggd2UgYWxyZWFkeSBoYXZlLiAgVG8gYXZvaWQgcmVseWluZyBvbiB0b28gc3BhcnNlIGRhdGEsIHdlIGV4Y2x1ZGUgY2VsbHMgZnJvbSBkb25vcnMgd2l0aCBmZXdlciB0aGFuIDQgY2VsbHMgaW4gYSBnaXZlbiB0eXBlLiAgVGhpcyBhbGxvd3MgdXMgdG8gcGVyZm9ybSByYW5kb20gZm9yZXN0IHByZWRpY3Rpb25zIHdpdGggZm91ciBncm91cHMgcGVyIGNlbGwgdHlwZS4gIEluIHRoaXMgZmlyc3Qgc2VjdGlvbiB3ZSBidWlsZCB2ZWN0b3JzIGZvciBjZWxscyB0byBrZWVwLiAgCiAgCmBgYHtyIHJlZmFjdG9yIGRhdGEsIHdhcm5pbmc9RkFMU0V9CnNldXJhdF9hbGwgPC0gZGF0QWxsCmZvciAocyBpbiBuYW1lcyhkYXRBbGwpKXsKICBrcGRuIDwtIG5hbWVzKHRhYmxlKGRhdEFsbFtbc11dJGRvbm9yX25hbWUpKVt0YWJsZShkYXRBbGxbW3NdXSRkb25vcl9uYW1lKT49NF0KICBrcCAgIDwtIGlzLmVsZW1lbnQoZGF0QWxsW1tzXV0kZG9ub3JfbmFtZSxpbnRlcnNlY3QoaHZzX2Rvbm9ycyxrcGRuKSkKICBzZXVyYXRfYWxsW1tzXV0gPC0gZGF0QWxsW1tzXV1bLGtwXQogIHNldXJhdF9hbGxbW3NdXSRkb25vcl9uYW1lIDwtIGRyb3BsZXZlbHMoc2V1cmF0X2FsbFtbc11dJGRvbm9yX25hbWUpCn0KYGBgCiAgCiAgCkxldCdzIHJ1biByYW5kb20gZm9yZXN0IHByZWRpY3Rpb24gZm9yIGFsbCBzdWJjbGFzc2VzIHVzaW5nIFBDcyBhcyBiYXNlbGluZSAoaGVscGVyIGZ1bmN0aW9ucyBhcmUgaW4gdGhlIHNvdXJjZWQgZnVuY3Rpb24gZmlsZSkuICBMZXQncyBydW4gdGhlIHJhbmRvbSBmb3Jlc3QgY2FsY3VsYXRpb25zLiAgTm90ZSB0aGF0IHRoaXMgaXMgZmFpcmx5IHNsb3cgKH5hIGNvdXBsZSBvZiBob3VycyB0byBydW4pLiAgVGhlIHNwZWNpZmljIGFsZ29yaXRobSBpcyBhcyBmb2xsb3dzOiAgCjEuIEZvciBlYWNoIHN1YmNsYXNzLCBzZWxlY3QgKHVwIHRvKSAyNCBjZWxscyBwZXIgZG9ub3IgIAoyLiBSdW4gcmFuZG9tIGZvcmVzdCBwcmVkaWN0aW9uIHVzaW5nIDc1JSB0cmFpbmluZywgMjUlIHRlc3QgZm91ciB0aW1lcyB0byBnZXQgcHJlZGljdGlvbnMgZm9yIGFsbCAyNCBjZWxscyAgCjMuIFJlcGVhdCBmaXJzdCB0d28gc3RlcHMgMjUgdGltZXMgdG8gY2FsY3VsYXRlIG1lYW4gYW5kIHN0YW5kYXJkIGRldmlhdGlvbiBvZiBSRiBwcmVkaWN0aW9uIHNjb3JlcyBwZXIgc3ViY2xhc3MsIHBlciBkb25vciAgCjQuIFVzZSB0aGVzZSB2YWx1ZXMgZm9yIGRvd25zdHJlYW0gYW5hbHlzaXMgIAogIApgYGB7ciByYW5kb20gZm9yZXN0IGNhbGN1bGF0aW9ufQpudW1iZXJPZlBlcm11dGF0aW9ucyA9IDI1CmNlbGxzUGVyUGVybXV0YXRpb24gID0gMjQKbnVtYmVyT2ZQQ3MgICAgICAgICAgPSAzMApwcmVkaWN0aW9ucyA8LSBsaXN0KCkKY29ycmVjdCAgICAgPC0gbWF0cml4KDAsbnJvdz1sZW5ndGgoc3ViY2xhc3NlcyksIG5jb2w9bnVtYmVyT2ZQZXJtdXRhdGlvbnMpCnJvd25hbWVzKGNvcnJlY3QpIDwtIHN1YmNsYXNzZXMKCnByaW50KHBhc3RlKGRhdGUoKSwiLSBzdGFydCIpKQpmb3IgKGkgaW4gMTpudW1iZXJPZlBlcm11dGF0aW9ucyl7CiAgcHJpbnQoaSkKICBwcmVkaWN0aW9uc1tbaV1dIDwtIGxpc3QoKQogICMgUnVuIHJhbmRvbSBmb3Jlc3QgcHJlZGljdGlvbgogIGZvciAoY3QgaW4gc3ViY2xhc3Nlcyl7CiAgICB0bXBTYW1wIDwtIHN1YnNhbXBsZUNlbGxzKHNldXJhdF9hbGxbW2N0XV0kZG9ub3JfbmFtZSxjZWxsc1BlclBlcm11dGF0aW9uLHNlZWQ9aSkKICAgIHByZWRpY3Rpb25zW1tpXV1bW2N0XV0gPC0gcmZfcHJlZGljdGlvbl9mcm9tX3NldXJhdChzZXVyYXRfYWxsW1tjdF1dWyx0bXBTYW1wXSwgcGNzPW51bWJlck9mUENzLCBzZWVkPTEpCiAgICBjb3JyZWN0W2N0LGldIDwtIG1lYW4ocHJlZGljdGlvbnNbW2ldXVtbY3RdXVssMl09PXByZWRpY3Rpb25zW1tpXV1bW2N0XV1bLDFdLCBuYS5ybT1UUlVFKQogIH0KfQpwcmludChwYXN0ZShkYXRlKCksIi0gZW5kIikpCmBgYAogIAogIApSZXBlYXQgdGhlc2UgY2FsY3VsYXRpb25zIHVzaW5nIHRoZSBtb3N0IHZhcmlhYmxlIGdlbmVzLiAgKipUSElTIFNFQ1RJT04gSVMgVkVSWSBTTE9XISEhICBJdCB3aWxsIGxpa2VseSB0YWtlIGEgZmV3IGRheXMgdG8gcnVuLioqCiAgCmBgYHtyIHJhbmRvbSBmb3Jlc3QgY2FsY3VsYXRpb24gdXNpbmcgdmFyaWFibGUgZ2VuZXN9Cm51bWJlck9mUGVybXV0YXRpb25zX3ZhciA9IG51bWJlck9mUGVybXV0YXRpb25zIApwcmVkaWN0aW9uc192YXIgPC0gaW1wb3J0YW5jZV92YXIgPC0gbGlzdCgpCmNvcnJlY3RfdmFyICAgICA8LSBtYXRyaXgoMCxucm93PWxlbmd0aChzdWJjbGFzc2VzKSwgbmNvbD1udW1iZXJPZlBlcm11dGF0aW9uc192YXIpCnJvd25hbWVzKGNvcnJlY3RfdmFyKSA8LSBzdWJjbGFzc2VzCgpwcmludChwYXN0ZShkYXRlKCksIi0gc3RhcnQiKSkKZm9yIChpIGluIDE6bnVtYmVyT2ZQZXJtdXRhdGlvbnNfdmFyKXsKICBwcmludChwYXN0ZShpLCItICAiLGRhdGUoKSkpCiAgcHJlZGljdGlvbnNfdmFyW1tpXV0gPC0gaW1wb3J0YW5jZV92YXJbW2ldXSA8LSBsaXN0KCkKICAjIFJ1biByYW5kb20gZm9yZXN0IHByZWRpY3Rpb24KICBmb3IgKGN0IGluIHN1YmNsYXNzZXMpewogICAgcHJpbnQoY3QpCiAgICB0bXBTYW1wIDwtIHN1YnNhbXBsZUNlbGxzKHNldXJhdF9hbGxbW2N0XV0kZG9ub3JfbmFtZSxjZWxsc1BlclBlcm11dGF0aW9uLHNlZWQ9aSkKICAgIHJmT3V0ICAgPC0gcmZfcHJlZGljdGlvbl9mcm9tX2xvZ0NQTShzZXVyYXRfYWxsW1tjdF1dWyx0bXBTYW1wXSkKICAgIHByZWRpY3Rpb25zX3ZhcltbaV1dW1tjdF1dIDwtIHJmT3V0JHByZWRpY3Rpb25zCiAgICBpbXBvcnRhbmNlX3ZhcltbaV1dW1tjdF1dICA8LSByZk91dCRpbXBvcnRhbmNlCiAgICBjb3JyZWN0X3ZhcltjdCxpXSA8LSBtZWFuKHByZWRpY3Rpb25zX3ZhcltbaV1dW1tjdF1dWywyXT09cHJlZGljdGlvbnNfdmFyW1tpXV1bW2N0XV1bLDFdLCBuYS5ybT1UUlVFKQogIH0KICBzYXZlKHByZWRpY3Rpb25zX3ZhcixpbXBvcnRhbmNlX3Zhcixjb3JyZWN0X3ZhcixjdCxpLGZpbGU9ImltcG9ydGFuY2VfZ2VuZXNfYW5kX3ByZWRpY3Rpb25zLlJEYXRhIikgIyBpbiBjYXNlIGl0IGNyYXNoZXMKfQpwcmludChwYXN0ZShkYXRlKCksIi0gZW5kIikpCmBgYAogIAogIApIb3cgZG8gdGhlIHZhbHVlcyBsb29rIGZvciBlYWNoIHN1YmNsYXNzIG92ZXJhbGw/IAogIApgYGB7ciBwcmVkaWN0aW9uIGFjY3VyYWN5IFJGIFBDQSwgZmlnLmhlaWdodD01LCBmaWcud2lkdGg9MTJ9Cm51bWJlck9mQ2VsbHMgPC0gcm93U3Vtcyhjb3JyZWN0KSowCmZvciAoY3QgaW4gcm93bmFtZXMoY29ycmVjdCkpIG51bWJlck9mQ2VsbHNbY3RdIDwtIGRpbShzZXVyYXRfYWxsW1tjdF1dKVsyXQoKcGx0IDwtIGFzLmdncGxvdChmdW5jdGlvbigpewpwYXIobWZyb3c9YygxLDIpKQp2ZXJib3NlU2NhdHRlcnBsb3Qocm93TWVhbnMoY29ycmVjdCksbnVtYmVyT2ZDZWxscyx4bGFiPSJNZWFuIFJGIHByZWRpY3Rpb24iLAogICAgICAgICAgICAgICAgICAgeWxhYj0iTnVtYmVyIG9mIHRvdGFsIGNlbGxzIixtYWluPSIiLGNvbD0id2hpdGUiLHBjaD0xOSx4bGltPWMoMC4yNSwwLjcpKQp0ZXh0KHJvd01lYW5zKGNvcnJlY3QpLG51bWJlck9mQ2VsbHMscm93bmFtZXMoY29ycmVjdCksY2V4PTAuNjUsY29sPXN1YmNsYXNzX2NvbG9yc1tyb3duYW1lcyhjb3JyZWN0KV0pCnZlcmJvc2VTY2F0dGVycGxvdChyb3dNZWFucyhjb3JyZWN0KSxhcHBseShjb3JyZWN0LDEsc2QpLHhsYWI9Ik1lYW4gUkYgcHJlZGljdGlvbiIsCiAgICAgICAgICAgICAgICAgICB5bGFiPSJTRCBvZiBSRiBwcmVkaWN0aW9uIixtYWluPSIiLGNvbD1zdWJjbGFzc19jb2xvcnNbcm93bmFtZXMoY29ycmVjdCldLAogICAgICAgICAgICAgICAgICAgcGNoPTE5LHhsaW09YygwLjI1LDAuNykpCmNlbGxzX3Blcl90eXBlIDwtIHBhc3RlMChyb3VuZChudW1iZXJPZkNlbGxzLzEwMCkvMTAsIksiKQp0ZXh0KHJvd01lYW5zKGNvcnJlY3QpLGFwcGx5KGNvcnJlY3QsMSxzZCkscGFzdGUwKHJvd25hbWVzKGNvcnJlY3QpLCIgKCIsY2VsbHNfcGVyX3R5cGUsIikiKSxjZXg9MC42NSkKfSkKcGx0Cmdnc2F2ZSgiUkZfcHJlZGljdGlvbl9zYW5pdHlDaGVja19zY2F0dGVycGxvdHMucGRmIiwgcGxvdD1wbHQsIGhlaWdodCA9IDYsIHdpZHRoID0gMTIpCgpsZXYgICA9IHJvd25hbWVzKGNvcnJlY3QpW2xlbmd0aChyb3duYW1lcyhjb3JyZWN0KSk6MV0gI1tvcmRlcihyb3dNZWFucyhjb3JyZWN0KSldICMgU2FtZSBvcmRlciBhcyB0cmVlLCBidXQgbmVlZHMgdG8gYmUgYmFja3dhcmRzIHRvIHdvcmsgd2l0aCBjb2RlLgpjbGFzc2VzICA9IHJlcCgiTm9uLW5ldXJvbmFsIixsZW5ndGgobGV2KSkKY2xhc3Nlc1tpcy5lbGVtZW50KGxldixzdWJjbGFzc2VzWzE6OV0pXSA9ICJHQUJBZXJnaWMiCmNsYXNzZXNbaXMuZWxlbWVudChsZXYsc3ViY2xhc3Nlc1sxMDoxOF0pXSA9ICJHbHV0YW1hdGVyZ2ljIgojY29scyA8LSBsYWJlbHMyY29sb3JzKGNsYXNzZXMpCmNvbHMgPC0gYygiI0ZGOTI4OSIsIiNDREExRkYiLCIjQ0FCRDAwIilbYygxLDEsMSwxLDEsMSwxLDEsMSwyLDIsMiwyLDIsMiwyLDIsMiwzLDMsMywzLDMsMyldWzI0OjFdICAjIE1hdGNoIGNvbG9ycyBiZWxvdwpuYW1lcyhjbGFzc2VzKSA8LSBuYW1lcyhjb2xzKSA8LSBsZXYKeG5hbWUgPSBmYWN0b3IocmVwKHJvd25hbWVzKGNvcnJlY3QpLCBuY29sKGNvcnJlY3QpKSxsZXZlbHM9bGV2KQoKcGx0IDwtIGFzLmdncGxvdChmdW5jdGlvbigpewojIyBMZXQncyBhbHNvIG1ha2UgYSBiYXJwbG90CnBhcihtZnJvdz1jKDEsMSkpCnBhcihtYXI9YygxMCw4LDUsMykpCnZlcmJvc2VCYXJwbG90KHVubGlzdChkYXRhLmZyYW1lKGNvcnJlY3QpKSx4bmFtZSxsYXM9Mix4bGFiPSIiLHlsYWI9IiIsbWFpbj0iRnJhY3Rpb24gY29ycmVjdGx5IHByZWRpY3RlZCIsY29sPWNvbHMseWxpbT1jKDAsMSkpCmFibGluZShoPSgwOjUpLzUsY29sPSJncmV5IixsdHk9ImRvdHRlZCIpCn0pCnBsdApnZ3NhdmUoIlJGX3ByZWRpY3Rpb25fYmFycGxvdF9sYXJnZS5wZGYiLCBwbG90PXBsdCwgaGVpZ2h0ID0gNiwgd2lkdGggPSAxMikKYGBgCiAgCkJhc2VkIG9uIHRoaXMgbWV0cmljLCBhbGwgb2YgdGhlIGdsdXRhbWF0ZXJnaWMgdHlwZXMgYW5kIG5vbi1uZXVyb25hbCBjZWxscyBoYXZlIGEgaGlnaGVyIHByZWRpY3RpdmUgcG93ZXIgKGFuZCB0aGVyZWZvcmUgbW9yZSBkb25vciB2YXJpYWJpbGl0eSkgdGhhbiBHQUJBZXJnaWMgdHlwZXMgYW5kIG5vbi1uZXVyYWwgdHlwZXMgKG1pbnVzIG1pY3JvZ2xpYSkuICBUaGlzIGFwcGVhcnMgcmVsYXRpdmVseSBpbmRlcGVuZGVudCBvZiBudW1iZXIgb2YgdG90YWwgY2VsbHMgcGVyIGdyb3VwIGFuZCBtYXRjaGVzIGV4cGVjdGF0aW9ucyAoYWx0aG91Z2ggaW50ZXJlc3RpbmdseSBMMi8zIGlzIGxvd2VyIHRoYW4gc29tZSBvZiB0aGUgb3RoZXIgdHlwZXMpLiAgSW4gYWRkaXRpb24gdGhlIHN0YW5kYXJkIGRldmlhdGlvbiBvZiB0aGUgUkYgcHJlZGljdGlvbiBpcyBxdWl0ZSBsb3cuICBGaW5hbGx5LCBhbGwgY2VsbCB0eXBlcyBoYXZlIHNvbWUgZG9ub3Igc2lnbmF0dXJlcy4gIEl0IG1heSBiZSB3b3J0aCB0ZXN0aW5nIHRoaXMgYWZ0ZXIgYWNjb3VudGluZyBmb3Igc3VwZXJ0eXBlcywgYnV0IG92ZXJhbGwgSSdtIGhhcHB5IHdpdGggdGhpcyByZXN1bHQuICAgCiAgClJlcGVhdCBmb3IgdGhlIHZhcmlhYmxlIGdlbmVzICh3aGljaCBpcyB3aGF0IHdhcyB1c2VkIGZvciB0aGUgbWFpbiBmaWd1cmUpLiAgCiAgCkhvdyBkbyB0aGUgdmFsdWVzIGxvb2sgZm9yIGVhY2ggc3ViY2xhc3Mgb3ZlcmFsbD8gCiAgCmBgYHtyIHByZWRpY3Rpb24gYWNjdXJhY3kgUkYgdmFyaWFibGUgZ2VuZXMsIGZpZy5oZWlnaHQ9NSwgZmlnLndpZHRoPTEyfQpudW1iZXJPZkNlbGxzIDwtIHJvd1N1bXMoY29ycmVjdF92YXIpKjAKZm9yIChjdCBpbiByb3duYW1lcyhjb3JyZWN0X3ZhcikpIG51bWJlck9mQ2VsbHNbY3RdIDwtIGRpbShzZXVyYXRfYWxsW1tjdF1dKVsyXQoKcGx0IDwtIGFzLmdncGxvdChmdW5jdGlvbigpewpwYXIobWZyb3c9YygxLDIpKQp2ZXJib3NlU2NhdHRlcnBsb3Qocm93TWVhbnMoY29ycmVjdF92YXIpLG51bWJlck9mQ2VsbHMseGxhYj0iTWVhbiBSRiBwcmVkaWN0aW9uIiwKICAgICAgICAgICAgICAgICAgIHlsYWI9Ik51bWJlciBvZiB0b3RhbCBjZWxscyIsbWFpbj0iIixjb2w9IndoaXRlIixwY2g9MTkseGxpbT1jKDAuMywwLjgpKQp0ZXh0KHJvd01lYW5zKGNvcnJlY3RfdmFyKSxudW1iZXJPZkNlbGxzLHJvd25hbWVzKGNvcnJlY3RfdmFyKSxjZXg9MC42NSxjb2w9c3ViY2xhc3NfY29sb3JzW3Jvd25hbWVzKGNvcnJlY3QpXSkKdmVyYm9zZVNjYXR0ZXJwbG90KHJvd01lYW5zKGNvcnJlY3RfdmFyKSxhcHBseShjb3JyZWN0X3ZhciwxLHNkKSx4bGFiPSJNZWFuIFJGIHByZWRpY3Rpb24iLAogICAgICAgICAgICAgICAgICAgeWxhYj0iU0Qgb2YgUkYgcHJlZGljdGlvbiIsbWFpbj0iIixjb2w9c3ViY2xhc3NfY29sb3JzW3Jvd25hbWVzKGNvcnJlY3RfdmFyKV0sCiAgICAgICAgICAgICAgICAgICBwY2g9MTkseGxpbT1jKDAuMywwLjgpKQpjZWxsc19wZXJfdHlwZSA8LSBwYXN0ZTAocm91bmQobnVtYmVyT2ZDZWxscy8xMDApLzEwLCJLIikKdGV4dChyb3dNZWFucyhjb3JyZWN0X3ZhciksYXBwbHkoY29ycmVjdF92YXIsMSxzZCkscGFzdGUwKHJvd25hbWVzKGNvcnJlY3RfdmFyKSwiICgiLGNlbGxzX3Blcl90eXBlLCIpIiksY2V4PTAuNjUpCn0pCnBsdApnZ3NhdmUoIlJGX3ByZWRpY3Rpb25fVkFSR0VORVNfc2FuaXR5Q2hlY2tfc2NhdHRlcnBsb3RzLnBkZiIsIHBsb3Q9cGx0LCBoZWlnaHQgPSA2LCB3aWR0aCA9IDEyKQoKCnBsdCA8LSBhcy5nZ3Bsb3QoZnVuY3Rpb24oKXsKcGFyKG1mcm93PWMoMSwyKSkKdmVyYm9zZVNjYXR0ZXJwbG90KHJvd01lYW5zKGNvcnJlY3QpLHJvd01lYW5zKGNvcnJlY3RfdmFyKSx4bGFiPSJNZWFuIFJGIHByZWRpY3Rpb24gKFBDcykiLAogICAgICAgICAgICAgICAgICAgeWxhYj0iTWVhbiBSRiBwcmVkaWN0aW9uIChWYXIuIEdlbmVzKSIsbWFpbj0iIixjb2w9ImJsYWNrIixjZXg9MC4zLAogICAgICAgICAgICAgICAgICAgcGNoPTE5LHhsaW09YygwLjIsMC44KSx5bGltPWMoMC4yLDAuOCkpCmFibGluZSgwLDEsY29sPSJncmV5IixsdHk9ImRhc2hlZCIpCnRleHQocm93TWVhbnMoY29ycmVjdCkscm93TWVhbnMoY29ycmVjdF92YXIpLHJvd25hbWVzKGNvcnJlY3QpLGNleD0wLjY1LGNvbD1zdWJjbGFzc19jb2xvcnNbcm93bmFtZXMoY29ycmVjdF92YXIpXSkKfSkKcGx0Cmdnc2F2ZSgiUkZfcHJlZGljdGlvbl9QQ3NfdnNfVkFSR0VORVMucGRmIiwgcGxvdD1wbHQsIGhlaWdodCA9IDYsIHdpZHRoID0gMTIpCgoKbGV2ICAgPSByb3duYW1lcyhjb3JyZWN0X3ZhcilbbGVuZ3RoKHJvd25hbWVzKGNvcnJlY3RfdmFyKSk6MV0gI1tvcmRlcihyb3dNZWFucyhjb3JyZWN0X3ZhcikpXSAjIFNhbWUgb3JkZXIgYXMgdHJlZSwgYnV0IG5lZWRzIHRvIGJlIGJhY2t3YXJkcyB0byB3b3JrIHdpdGggY29kZS4KY2xhc3NlcyAgPSByZXAoIk5vbi1uZXVyb25hbCIsbGVuZ3RoKGxldikpCmNsYXNzZXNbaXMuZWxlbWVudChsZXYsc3ViY2xhc3Nlc1sxOjldKV0gPSAiR0FCQWVyZ2ljIgpjbGFzc2VzW2lzLmVsZW1lbnQobGV2LHN1YmNsYXNzZXNbMTA6MThdKV0gPSAiR2x1dGFtYXRlcmdpYyIKI2NvbHMgPC0gbGFiZWxzMmNvbG9ycyhjbGFzc2VzKQpjb2xzIDwtIGMoIiNGRjkyODkiLCIjQ0RBMUZGIiwiI0NBQkQwMCIpW2MoMSwxLDEsMSwxLDEsMSwxLDEsMiwyLDIsMiwyLDIsMiwyLDIsMywzLDMsMywzLDMpXVsyNDoxXSAgIyBNYXRjaCBjb2xvcnMgYmVsb3cKbmFtZXMoY2xhc3NlcykgPC0gbmFtZXMoY29scykgPC0gbGV2CnhuYW1lID0gZmFjdG9yKHJlcChyb3duYW1lcyhjb3JyZWN0X3ZhciksIG5jb2woY29ycmVjdF92YXIpKSxsZXZlbHM9bGV2KQoKcGx0IDwtIGFzLmdncGxvdChmdW5jdGlvbigpewojIyBMZXQncyBhbHNvIG1ha2UgYSBiYXJwbG90CnBhcihtZnJvdz1jKDEsMSkpCnBhcihtYXI9YygxMCw4LDUsMykpCnZlcmJvc2VCYXJwbG90KHVubGlzdChkYXRhLmZyYW1lKGNvcnJlY3RfdmFyKSkseG5hbWUsbGFzPTIseGxhYj0iIix5bGFiPSIiLG1haW49IkZyYWN0aW9uIGNvcnJlY3RseSBwcmVkaWN0ZWQiLGNvbD1jb2xzLHlsaW09YygwLDEpKQphYmxpbmUoaD0oMDo1KS81LGNvbD0iZ3JleSIsbHR5PSJkb3R0ZWQiKQp9KQpwbHQKZ2dzYXZlKCJSRl9wcmVkaWN0aW9uX1ZBUkdFTkVTX2JhcnBsb3RfbGFyZ2UucGRmIiwgcGxvdD1wbHQsIGhlaWdodCA9IDYsIHdpZHRoID0gMTIpCmBgYAogIAogIApMZXQncyByZXRyeSBvbiBhIHBlci1kb25vciBsZXZlbCB0byBzZWUgaWYgdGhlIHNhbWUgZG9ub3JzIGFyZSBvdXRsaWVycyBmb3IgZWFjaCBjZWxsIHR5cGUuICAKICAKYGBge3IgZG9ub3ItbGV2ZWwgcmVzdWx0cywgZmlnLmhlaWdodD01LjUsIGZpZy53aWR0aD04LjV9CmRvbm9ycyA8LSB1bmlxdWUocHJlZGljdGlvbnNbWzFdXVtbIkwyLzMgSVQiXV1bLCJkb25vciJdKQpkb25zICAgPC0gc29ydCh1bmlxdWUoZG9ub3JzKSkKYWdyZWVtZW50cyA8LSBtYXRyaXgoMCxucm93ID0gbGVuZ3RoKHN1YmNsYXNzZXMpLG5jb2w9bGVuZ3RoKGRvbnMpKQpyb3duYW1lcyhhZ3JlZW1lbnRzKSA8LSBzdWJjbGFzc2VzCmNvbG5hbWVzKGFncmVlbWVudHMpIDwtIGRvbnMKZm9yIChjdCBpbiBzdWJjbGFzc2VzKSBmb3IgKGRuIGluIGRvbnMpewogIHRtcCA8LSBOVUxMCiAgZm9yIChudW0gaW4gMTpudW1iZXJPZlBlcm11dGF0aW9ucykKICAgIHRtcCA8LSBjKHRtcCwgc3VtKChwcmVkaWN0aW9uc192YXJbW251bV1dW1tjdF1dWywxXT09ZG4pJihwcmVkaWN0aW9uc192YXJbW251bV1dW1tjdF1dWywyXT09ZG4pLAogICAgICAgICAgICAgICAgICAgICAgICAgICBuYS5ybT1UUlVFKSAvIHN1bShwcmVkaWN0aW9uc192YXJbW251bV1dW1tjdF1dWywxXT09ZG4sbmEucm09VFJVRSkpCiAgYWdyZWVtZW50c1tjdCxkbl0gPSBtZWFuKHRtcCxybS5uYT1UUlVFKQp9CmFncmVlbWVudHNbaXMubmFuKGFncmVlbWVudHMpXSA9IE5BCgojQ3JlYXRlIGhlYXRtYXAgdXNpbmcgcGhlYXRtYXAgCmFncmVlbWVudHMgPC0gYWdyZWVtZW50c1tsZXZbbGVuZ3RoKGxldik6MV0sXSAgIyBSZW9yZGVyIHRvIG1hdGNoIGFib3ZlCmNvbF9tZXRhID0gZGF0YS5mcmFtZShkb25vcl9tZXRhZGF0YVtkb25zLGMoIlNleCIsIkRpc2Vhc2UiLCJST0kiKV0pCnBoZWF0IDwtIHBoZWF0bWFwKGFncmVlbWVudHMsIHNob3dfcm93bmFtZXM9VFJVRSwgY2x1c3Rlcl9jb2xzPVRSVUUsIGNsdXN0ZXJfcm93cz1GQUxTRSwgICMgUm93cyBzaG91bGQgbWF0Y2ggYWJvdmUgCiAgICAgICAgICAgICAgICBzY2FsZT0ibm9uZSIsIGFubm90YXRpb25fY29sID0gY29sX21ldGEsIGFubm90YXRpb25fcm93ID0gZGF0YS5mcmFtZShjZWxsdHlwZT1jbGFzc2VzW3Jvd25hbWVzKGFncmVlbWVudHMpXSksCiAgICAgICAgICAgICAgICBjbHVzdGVyaW5nX2Rpc3RhbmNlX3Jvd3M9ImV1Y2xpZGVhbiIsIGNleD0xLCBjbHVzdGVyaW5nX2Rpc3RhbmNlX2NvbHM9ImV1Y2xpZGVhbiIsIAogICAgICAgICAgICAgICAgY2x1c3RlcmluZ19tZXRob2Q9ImNvbXBsZXRlIiwgYm9yZGVyX2NvbG9yPUZBTFNFLCBtYWluPSJGcmFjdGlvbiBvZiBjZWxscyBjb3JyZWN0bHkgbWFwcGVkIHRvIGRvbm9yIikKcGhlYXQKZ2dzYXZlKCJSRl9wcmVkaWN0aW9uX21haW5fcGhlYXRtYXBfcGxvdC5wZGYiLCBwbG90PXBoZWF0LCBoZWlnaHQgPSA2LCB3aWR0aCA9IDkpCm91dGxpZXJGYWN0b3IgPC0gY29sTWVhbnMoYWdyZWVtZW50cyxuYS5ybT1UUlVFKQp3cml0ZS5jc3YoYWdyZWVtZW50cywiY29ycmVjdF9SRl9tYXBwaW5nX2ZyYWN0aW9uLmNzdiIpCmBgYAogIAogIApQbG90IHRoZSBtZWRpYW4gdmFsdWUgYWdhaW4gYSBiaXQgc21hbGxlciBmb3IgcGxvdHRpbmcgcHVycG9zZXMuICAKICAKYGBge3IgcGxvdCB0cmVlIG9yZGVyLCBmaWcuaGVpZ2h0PTQuNSwgZmlnLndpZHRoPTZ9CnN1YmNsYXNzX29yZGVyIDwtIGxldltsZW5ndGgobGV2KToxXQpwbHQgPC0gYXMuZ2dwbG90KGZ1bmN0aW9uKCl7CnBhcihtZnJvdz1jKDEsMSkpCnBhcihtYXI9YygxMCw4LDMsMykpCm9yZCAgIDwtIHN1YmNsYXNzX29yZGVyW2xlbmd0aChzdWJjbGFzc19vcmRlcik6MV0KeG5hbWUgPC0gZmFjdG9yKGFzLmNoYXJhY3Rlcih4bmFtZSksbGV2ZWxzPW9yZCkKdmVyYm9zZUJhcnBsb3QodW5saXN0KGRhdGEuZnJhbWUoY29ycmVjdF92YXIpKSx4bmFtZSxsYXM9Mix4bGFiPSIiLHlsYWI9IkZyYWN0aW9uIGNvcnJlY3RseSBwcmVkaWN0ZWQiLAogICAgICAgICAgICAgICBjb2w9Y29sc1tvcmRdLHlsaW09YygwLDEpLCBjZXg9MC41LCBjZXguYXhpcyA9IDEsIGNleC5sYWIgPSAxKQphYmxpbmUoaD0oMDo1KS81LGNvbD0iZ3JleSIsbHR5PSJkb3R0ZWQiKQphYmxpbmUoaD1tZWRpYW4odW5saXN0KGRhdGEuZnJhbWUoY29ycmVjdF92YXIpKSksY29sPSJibGFjayIsbHdkPTIpCn0pCnBsdApnZ3NhdmUoIlJGX3ByZWRpY3Rpb25fYmFycGxvdF9sYXJnZS5wZGYiLCBwbG90PXBsdCwgaGVpZ2h0ID0gNC41LCB3aWR0aCA9IDYpCmBgYAogIAogIApUaGVyZSBpcyBhIGNsZWFyIGNlbGwgdHlwZSBzZXBhcmF0aW9uIGJ5IGNsYXNzLCBhcyBkaXNjdXNzZWQgYWJvdmUgKHJvdyBjb2xvcnMpLCBidXQgdGhlIGRvbm9yIHNlcGFyYXRpb24gYmV0d2VlbiBkb25vciBkZW1vZ3JhcGhpY3MgaXMgbGVzcyBvYnZpb3VzLiAgTGV0J3MgcXVhbnRpZnkgdGhpcyBzaWduaWZpY2FuY2Ugb2YgdGhpcyB1c2luZyBhIGxpbmVhciBtb2RlbC4gIAoKYGBge3IgbGluZWFyIG1vZGVsIFJGIHByZWRpY3Rpb25zfQpsbV9wdmFsX2FwcGx5KGNvbE1lYW5zKGFncmVlbWVudHMsbmEucm09VFJVRSlba3BfZG9ub3JzM10pICAjIEZ1bmN0aW9uIGlzIGluIHRoZSBleHRyYV9mdW5jdGlvbnMuciBzY3JpcHQKYGBgCiAgCiAgClBsb3QgYSBkb25vciBiYXIgcGxvdCBhcyB3ZWxsLiAgCiAgCmBgYHtyIHBsb3QgdHJlZSBvcmRlciBwZXIgZG9ub3IsIGZpZy5oZWlnaHQ9MS41LCBmaWcud2lkdGg9OH0Kc3ViY2xhc3Nfb3JkZXIgPC0gbGV2W2xlbmd0aChsZXYpOjFdCnBsdCA8LSBhcy5nZ3Bsb3QoZnVuY3Rpb24oKXsKcGFyKG1mcm93PWMoMSwxKSkKcGFyKG1hcj1jKDEwLDgsMywzKSkKdmFsICAgPC0gLWFwcGx5KGFncmVlbWVudHMsMixtZWRpYW4sbmEucm09VFJVRSlbcGhlYXQkdHJlZV9jb2wkb3JkZXJdCm5hbWVzKHZhbCkgPC0gTlVMTApiYXJwbG90KHZhbCx5bGltPWMoLTEsMCksYm9yZGVyID0gRkFMU0UsIGNleC5heGlzID0gMC41LCBjZXgubGFiID0gMC41LCBsYXM9MiwKICAgICAgICB5bGFiPSJSRiBhY2N1cmFjeSAobWVkaWFuKSIsIHhsYWI9IiIsbWFpbj0iIikKYWJsaW5lKGg9LSgwOjUpLzUsY29sPSJncmV5IixsdHk9ImRvdHRlZCIpCmFibGluZShoPS1tZWRpYW4odW5saXN0KGRhdGEuZnJhbWUoY29ycmVjdF92YXIpKSksY29sPSJibGFjayIsbHdkPTIpCn0pCnBsdApnZ3NhdmUoIlJGX3ByZWRpY3Rpb25fcGVyX2Rvbm9yX2JhcnBsb3QucGRmIiwgcGxvdD1wbHQsIGhlaWdodCA9IDEuNSwgd2lkdGggPSA4KQpgYGAKICAKICAKU2lnbmlmaWNhbnQgc2VwYXJhdGlvbiBieSBzZXggYmFzZWQgb24gbGluZWFyIG1vZGVsLiAgTGV0J3MgcGxvdCB0aGVzZSB2YWx1ZXMgYW5kIGNhbGN1bGF0ZSBLcnVza2FsLVdhbGxhY2UgdGVzdCBwLXZhbHVlcyBhcyB3ZWxsLiAgICAKICAKYGBge3IgUkYgcGxvdHMgYnkgY2xhc3MgcmVnaW9uIGFuZCBzZXgsIGZpZy5oZWlnaHQ9NSxmaWcud2lkdGg9MTR9CmFsbEdyb3Vwc0QgPC0gZG9ub3JfbWV0YWRhdGFba3BfZG9ub3JzMywiRGlzZWFzZSJdCmFsbEdyb3Vwc1IgPC0gZG9ub3JfbWV0YWRhdGFbaHZzX2Rvbm9ycywiUk9JIl0KYWxsR3JvdXBzUyA8LSBkb25vcl9tZXRhZGF0YVtodnNfZG9ub3JzLCJTZXgiXQoKY29sb3JfcGFsYXR0ZSA8LSBzZXROYW1lcyhjKCIjRkY4NkZGIiwiIzNEQzFGRiIsIiNGRjg2RkYiLCIjMDBENUZBIiwiIzdGQ0QwMCIsIiNGOUE4MEYiLCIjMDBEOTc5IiwiI0ZGOTI4OSIsIiNDREExRkYiLCIjQ0FCRDAwIiksCiAgICAgICAgICAgICAgICAgICAgICAgICAgYygiRXBpbGVwc3kiLCJUdW1vciIsIkZSTyIsIk1URyIsIlBDeCIsIkYiLCJNIiwiR0FCQWVyZ2ljIiwiR2x1dGFtYXRlcmdpYyIsIk5vbi1uZXVyb25hbCIpKQoKcGx0IDwtIGFzLmdncGxvdChmdW5jdGlvbigpewpwYXIobWZyb3c9YygxLDQpKQpwYXIobWFyPWMoMTIsOSw0LDEpKQp2ZXJib3NlQmFycGxvdChjb2xNZWFucyhhZ3JlZW1lbnRzWyxrcF9kb25vcnMzXSxuYS5ybT1UUlVFKSxhbGxHcm91cHNELHlsYWI9IlJGIHByZWRpY3Rpb24iLG1haW49IiIsIAogICAgICAgICAgICAgICB4bGFiPSIiLHlsaW09YygwLDEpLCBsYXM9MiwgY29sPWNvbG9yX3BhbGF0dGVbYygiRXBpbGVwc3kiLCJUdW1vciIpXSxwdC5jb2w9ImJsYWNrIixLcnVza2FsVGVzdCA9IFRSVUUsCiAgICAgICAgICAgICAgIGFkZFNjYXR0ZXJwbG90ID0gVFJVRSwgcGNoPTE5LCBwdC5jZXg9MS41LCBjZXgubGFiPTEuNSwgY2V4LmF4aXMgPSAxLCBjZXgubWFpbiA9IDEuNSkKcGFyKG1hcj1jKDEyLDcsNCwxKSkKdmVyYm9zZUJhcnBsb3QoY29sTWVhbnMoYWdyZWVtZW50c1ssaHZzX2Rvbm9yc10sbmEucm09VFJVRSksYWxsR3JvdXBzUix5bGFiPSJSRiBwcmVkaWN0aW9uIixtYWluPSIiLCAKICAgICAgICAgICAgICAgeGxhYj0iIix5bGltPWMoMCwxKSwgbGFzPTIsIGNvbD1jb2xvcl9wYWxhdHRlW2MoIkZSTyIsIk1URyIsIlBDeCIpXSxwdC5jb2w9ImJsYWNrIixLcnVza2FsVGVzdCA9IFRSVUUsCiAgICAgICAgICAgICAgIGFkZFNjYXR0ZXJwbG90ID0gVFJVRSwgcGNoPTE5LCBwdC5jZXg9MS41LCBjZXgubGFiPTEuNSwgY2V4LmF4aXMgPSAxLCBjZXgubWFpbiA9IDEuNSkKcGFyKG1hcj1jKDEyLDksNCwxKSkKdmVyYm9zZUJhcnBsb3QoY29sTWVhbnMoYWdyZWVtZW50c1ssaHZzX2Rvbm9yc10sbmEucm09VFJVRSksYWxsR3JvdXBzUyx5bGFiPSJSRiBwcmVkaWN0aW9uIixtYWluPSIiLCAKICAgICAgICAgICAgICAgeGxhYj0iIix5bGltPWMoMCwxKSwgbGFzPTIsIGNvbD1jb2xvcl9wYWxhdHRlW2MoIkYiLCJNIildLHB0LmNvbD0iYmxhY2siLEtydXNrYWxUZXN0ID0gVFJVRSwKICAgICAgICAgICAgICAgYWRkU2NhdHRlcnBsb3QgPSBUUlVFLCBwY2g9MTksIHB0LmNleD0xLjUsIGNleC5sYWI9MS41LCBjZXguYXhpcyA9IDEsIGNleC5tYWluID0gMS41KQpwYXIobWFyPWMoMTIsNyw0LDEpKQp2ZXJib3NlQmFycGxvdChyb3dNZWFucyhjb3JyZWN0W3N1YmNsYXNzZXMsXSxuYS5ybT1UUlVFKSxjbGFzc2VzW3N1YmNsYXNzZXNdLHlsYWI9IlJGIHByZWRpY3Rpb24iLAogICAgICAgICAgICAgICB4bGFiPSIiLHlsaW09YygwLDEpLCBsYXM9MiwgY29sPWNvbG9yX3BhbGF0dGVbdW5pcXVlKGNsYXNzZXNbc3ViY2xhc3Nlc10pXSxwdC5jb2w9ImJsYWNrIixLcnVza2FsVGVzdCA9IFRSVUUsCiAgICAgICAgICAgICAgIG1haW49IiIsYWRkU2NhdHRlcnBsb3QgPSBUUlVFLCBwY2g9MTksIHB0LmNleD0xLjUsIGNleC5sYWI9MS41LCBjZXguYXhpcyA9IDEsIGNleC5tYWluID0gMS41KQp9KQpwbHQKZ2dzYXZlKCJSRnByZWRpY3Rpb25fYmFycGxvdHNfYXZlcmFnZWQucGRmIiwgcGxvdD1wbHQsIGhlaWdodCA9IDUsIHdpZHRoID0gMTQpCmBgYAogIAogIApOb3cgbGV0J3Mgc2VlIGhvdyB0aGVzZSBSRiBwcmVkaWN0aW9ucyBkaWZmZXIgYWNyb3NzIGRvbm9yIG1ldGFkYXRhIGJ5IHN1YmNsYXNzIGFzIHdlIGRpZCB3aXRoIGFidW5kYW5jZXMgYWJvdmUuICAKICAKYGBge3IgbGluZWFyIG1vZGVsIFJGIHByZWRpY3Rpb25zIGJ5IHN1YmNsYXNzfQojY24gPC0gYygiRGlzZWFzZSIsIlJPSSIsIlNleCIpCmNuIDwtIGMoIkRpc2Vhc2UiLCJST0kiLCJTZXgiLCJUaXNzdWUuU291cmNlIiwiQWdlIikKUkZhZ3JlZW1lbnQgPC0gdChhZ3JlZW1lbnRzW3N1YmNsYXNzZXMsa3BfZG9ub3JzM10pCmxtX3B2YWxSRiA8LSBhcHBseShSRmFncmVlbWVudCwyLGxtX3B2YWxfYXBwbHkpICMgVGhpcyBmdW5jdGlvbiBpcyBpbiB0aGUgZXh0cmFfZnVuY3Rpb25zLnIgZmlsZQpyb3duYW1lcyhsbV9wdmFsUkYpIDwtIGNuCmRhdGEuZnJhbWUodChsbV9wdmFsUkYpKQpsbV9wdmFsX2FkanVzdFJGIDwtIGFwcGx5KGxtX3B2YWxSRiwyLHAuYWRqdXN0KSAgIyBGRFIgY29ycmVjdGVkCmRhdGEuZnJhbWUodChsbV9wdmFsX2FkanVzdFJGKSkKYGBgCiAgCiAgCmBgYHtyIHBlciBzdWJjbGFzcyBSRiBwbG90cyBieSByZWdpb24gYW5kIHNleCwgZmlnLmhlaWdodD0xMCxmaWcud2lkdGg9MTR9CnBsdCA8LSBhcy5nZ3Bsb3QoZnVuY3Rpb24oKXsKcGFyKG1mcm93PWMoNCw2KSkKcGFyKG1hcj1jKDIsNS41LDIsMSkpCmZvciAocyBpbiBzdWJjbGFzc2VzKQogIHZlcmJvc2VCYXJwbG90KGFncmVlbWVudHNbcyxrcF9kb25vcnMzXSxhbGxHcm91cHNELHlsYWI9cyxtYWluPSIiLEtydXNrYWxUZXN0ID0gVFJVRSwgeGxhYj0iIiwKICAgICAgICAgICAgICAgICB5bGltPWMoMCwxKSwgbGFzPTIsIHB0LmNvbD1zdWJjbGFzc19jb2xvcnNbc10sY29sPWNvbG9yX3BhbGF0dGVbYygiRXBpbGVwc3kiLCJUdW1vciIpXSwKICAgICAgICAgICAgICAgICBhZGRTY2F0dGVycGxvdCA9IFRSVUUsIHBjaD0xOSwgcHQuY2V4PTEsIGNleC5sYWI9MS41LCBjZXguYXhpcyA9IDAuNywgY2V4Lm1haW4gPSAxLjUpCn0pCnBsdApnZ3NhdmUoIlJGcHJlZGljdGlvbl9iYXJwbG90c19ieV9zdWJjbGFzc19kaXNlYXNlLnBkZiIsIHBsb3Q9cGx0LCBoZWlnaHQgPSAxMCwgd2lkdGggPSAxNCkKCnBsdCA8LSBhcy5nZ3Bsb3QoZnVuY3Rpb24oKXsKcGFyKG1mcm93PWMoNCw2KSkKcGFyKG1hcj1jKDIsNC43LDIsMSkpCmZvciAocyBpbiBzdWJjbGFzc2VzKQogIHZlcmJvc2VCYXJwbG90KGFncmVlbWVudHNbcyxodnNfZG9ub3JzXSxhbGxHcm91cHNSLHlsYWI9cyxtYWluPSIiLEtydXNrYWxUZXN0ID0gVFJVRSwgeGxhYj0iIiwKICAgICAgICAgICAgICAgICB5bGltPWMoMCwxKSwgbGFzPTIsIHB0LmNvbD1zdWJjbGFzc19jb2xvcnNbc10sY29sPWNvbG9yX3BhbGF0dGVbYygiRlJPIiwiTVRHIiwiUEN4IildLAogICAgICAgICAgICAgICAgIGFkZFNjYXR0ZXJwbG90ID0gVFJVRSwgcGNoPTE5LCBwdC5jZXg9MSwgY2V4LmxhYj0xLjUsIGNleC5heGlzID0gMC43LCBjZXgubWFpbiA9IDEuNSkKfSkKcGx0Cmdnc2F2ZSgiUkZwcmVkaWN0aW9uX2JhcnBsb3RzX2J5X3N1YmNsYXNzX3JlZ2lvbi5wZGYiLCBwbG90PXBsdCwgaGVpZ2h0ID0gMTAsIHdpZHRoID0gMTQpCgpwbHQgPC0gYXMuZ2dwbG90KGZ1bmN0aW9uKCl7CnBhcihtZnJvdz1jKDQsNikpCnBhcihtYXI9YygyLDUuNSwyLDEpKQpmb3IgKHMgaW4gc3ViY2xhc3NlcykKICB2ZXJib3NlQmFycGxvdChhZ3JlZW1lbnRzW3MsaHZzX2Rvbm9yc10sYWxsR3JvdXBzUyx5bGFiPXMsbWFpbj0iIixLcnVza2FsVGVzdCA9IFRSVUUsIHhsYWI9IiIsCiAgICAgICAgICAgICAgICAgeWxpbT1jKDAsMSksIGxhcz0yLCBwdC5jb2w9c3ViY2xhc3NfY29sb3JzW3NdLGNvbD1jb2xvcl9wYWxhdHRlW2MoIkYiLCJNIildLAogICAgICAgICAgICAgICAgIGFkZFNjYXR0ZXJwbG90ID0gVFJVRSwgcGNoPTE5LCBwdC5jZXg9MSwgY2V4LmxhYj0xLjUsIGNleC5heGlzID0gMC43LCBjZXgubWFpbiA9IDEuNSkKfSkKcGx0Cmdnc2F2ZSgiUkZwcmVkaWN0aW9uX2JhcnBsb3RzX2J5X3N1YmNsYXNzX3NleC5wZGYiLCBwbG90PXBsdCwgaGVpZ2h0ID0gMTAsIHdpZHRoID0gMTQpCmBgYAogIAogIApTZWUgd2hpY2ggZ2VuZXMgYXJlIG1vc3QgaW5mb3JtYXRpdmUgaW4gdGhlIHZhcmlvdXMgUkYgbW9kZWxzLgogIApgYGB7ciBSRiBnZW5lIGltcG9ydGFuY2V9CmdlbmVfaW1wb3J0YW5jZXMgPC0gTlVMTApmb3IgKGN0IGluIHN1YmNsYXNzZXMpewogIHByaW50KGN0KQogIGl2IDwtIE5VTEwKICBmb3IgKGkgaW4gMTpudW1iZXJPZlBlcm11dGF0aW9ucykgZm9yIChqIGluIDE6NCl7CiAgICBsICAgPC0gZGltKGltcG9ydGFuY2VfdmFyW1tpXV1bW2N0XV1bW2pdXSlbMl0KICAgIHRtcCA8LSBkYXRhLmZyYW1lKGdlbmU9cm93Lm5hbWVzKGltcG9ydGFuY2VfdmFyW1tpXV1bW2N0XV1bW2pdXSksaW1wb3J0YW5jZV92YXJbW2ldXVtbY3RdXVtbal1dWywobC0xKTpsXSkKICAgIGl2ICA8LSByYmluZChpdix0bXApCiAgfQogIGl2X21lYW4gPC0gaXYgJT4lIGdyb3VwX2J5KGdlbmUpICU+JSBzdW1tYXJpc2UobWVhbj1tZWFuKE1lYW5EZWNyZWFzZUFjY3VyYWN5KSkKICBpdl9tZWFuIDwtIGFzLmRhdGEuZnJhbWUoaXZfbWVhbltvcmRlcigtaXZfbWVhbiRtZWFuKSxdKQogIGdlbmVfaW1wb3J0YW5jZXMgPC0gcmJpbmQoZ2VuZV9pbXBvcnRhbmNlcyxkYXRhLmZyYW1lKHN1YmNsYXNzPWN0LGl2X21lYW4pKQp9CgojIFN1bW1hcml6ZSBieSBzdWJjbGFzcwpnZW5lX2ltcG9ydGFuY2VzX21lYW4gPC0gc3ByZWFkKGdlbmVfaW1wb3J0YW5jZXMsIHN1YmNsYXNzLCBtZWFuKSAKcm93bmFtZXMoZ2VuZV9pbXBvcnRhbmNlc19tZWFuKSA8LSBnZW5lX2ltcG9ydGFuY2VzX21lYW4kZ2VuZQpnZW5lX2ltcG9ydGFuY2VzX21lYW4gPC0gYXMubWF0cml4KGdlbmVfaW1wb3J0YW5jZXNfbWVhblssMjpkaW0oZ2VuZV9pbXBvcnRhbmNlc19tZWFuKVsyXV0pCgpoZWFkKHNvcnQoLXJvd01lYW5zKGdlbmVfaW1wb3J0YW5jZXNfbWVhbixuYS5ybT1UUlVFKSksNTApCmBgYAogIApTcGxpdCBieSBzZXguICAKICAKYGBge3IgUkYgZ2VuZSBtYWxlIGZlbWFsZX0KbWFsZSAgIDwtIGh2c19kb25vcnNbZG9ub3JfbWV0YWRhdGFbaHZzX2Rvbm9ycywiU2V4Il09PSJNIl0KZmVtYWxlIDwtIGh2c19kb25vcnNbZG9ub3JfbWV0YWRhdGFbaHZzX2Rvbm9ycywiU2V4Il09PSJGIl0KbWFsZV9pbXBvcnRhbmNlcyA8LSBmZW1hbGVfaW1wb3J0YW5jZXMgPC0gTlVMTApmb3IgKGN0IGluIHN1YmNsYXNzZXMpewogIHByaW50KGN0KQogIGl2IDwtIE5VTEwKICBmb3IgKGkgaW4gMTpudW1iZXJPZlBlcm11dGF0aW9ucykgZm9yIChqIGluIDE6NCl7CiAgICB4ICAgPC0gaW1wb3J0YW5jZV92YXJbW2ldXVtbY3RdXVtbal1dCiAgICBtICAgPC0gaW50ZXJzZWN0KG1hbGUsY29sbmFtZXMoeCkpCiAgICBmICAgPC0gaW50ZXJzZWN0KGZlbWFsZSxjb2xuYW1lcyh4KSkKICAgIHRtcCA8LSBkYXRhLmZyYW1lKGdlbmU9cm93Lm5hbWVzKHgpLCBtdmFsID0gcm93TWVhbnMoeFssbV0pLCBmdmFsID0gcm93TWVhbnMoeFssZl0pKQogICAgaXYgIDwtIHJiaW5kKGl2LHRtcCkKICB9CiAgaXZfbSA8LSBpdiAlPiUgZ3JvdXBfYnkoZ2VuZSkgJT4lIHN1bW1hcmlzZShtZWFuPW1lYW4obXZhbCkpCiAgaXZfbSA8LSBhcy5kYXRhLmZyYW1lKGl2X21bb3JkZXIoLWl2X20kbWVhbiksXSkKICBpdl9mIDwtIGl2ICU+JSBncm91cF9ieShnZW5lKSAlPiUgc3VtbWFyaXNlKG1lYW49bWVhbihmdmFsKSkKICBpdl9mIDwtIGFzLmRhdGEuZnJhbWUoaXZfZltvcmRlcigtaXZfZiRtZWFuKSxdKQogIG1hbGVfaW1wb3J0YW5jZXMgICA8LSByYmluZChtYWxlX2ltcG9ydGFuY2VzLGRhdGEuZnJhbWUoc3ViY2xhc3M9Y3QsaXZfbSkpCiAgZmVtYWxlX2ltcG9ydGFuY2VzIDwtIHJiaW5kKGZlbWFsZV9pbXBvcnRhbmNlcyxkYXRhLmZyYW1lKHN1YmNsYXNzPWN0LGl2X2YpKQp9CgojIFN1bW1hcml6ZSBieSBzdWJjbGFzcwptYWxlX2ltcG9ydGFuY2VzX21lYW4gPC0gc3ByZWFkKG1hbGVfaW1wb3J0YW5jZXMsIHN1YmNsYXNzLCBtZWFuKSAKcm93bmFtZXMobWFsZV9pbXBvcnRhbmNlc19tZWFuKSA8LSBtYWxlX2ltcG9ydGFuY2VzX21lYW4kZ2VuZQptYWxlX2ltcG9ydGFuY2VzX21lYW4gPC0gYXMubWF0cml4KG1hbGVfaW1wb3J0YW5jZXNfbWVhblssMjpkaW0obWFsZV9pbXBvcnRhbmNlc19tZWFuKVsyXV0pCmZlbWFsZV9pbXBvcnRhbmNlc19tZWFuIDwtIHNwcmVhZChmZW1hbGVfaW1wb3J0YW5jZXMsIHN1YmNsYXNzLCBtZWFuKSAKcm93bmFtZXMoZmVtYWxlX2ltcG9ydGFuY2VzX21lYW4pIDwtIGZlbWFsZV9pbXBvcnRhbmNlc19tZWFuJGdlbmUKZmVtYWxlX2ltcG9ydGFuY2VzX21lYW4gPC0gYXMubWF0cml4KGZlbWFsZV9pbXBvcnRhbmNlc19tZWFuWywyOmRpbShmZW1hbGVfaW1wb3J0YW5jZXNfbWVhbilbMl1dKQpgYGAKICAKU29tZSBwbG90cyB1c2luZyB0aGlzIGluZm9ybWF0aW9uIHdpbGwgc2hvdyB1cCBsYXRlciBpbiB0aGUgc2NyaXB0LiAgCiAgCiAgCiMjIEhpZ2ggdmFyaWFuY2UgZ2VuZXMgKEZpZyAxKQogIApUaGlzIHNlY3Rpb24gaXMgYWltZWQgYXQgaWRlbnRpZnlpbmcgdGhlIGdlbmVzIHdpdGggc3Ryb25nIGRvbm9yIHNpZ25hdHVyZXMuICBNb3JlIHNwZWNpZmljYWxseSwgd2Ugc2VlayB0byBhc2sgd2hpY2ggZ2VuZXMgaGF2ZSB2YXJpYW5jZSB0aGF0IGlzIGhpZ2hseSBkb25vci1kZXBlbmRlbnQgKGUuZy4sIHJhbmRvbSBjZWxsIHBhaXJzIGZyb20gZGlmZmVyZW50IGRvbm9ycyBhcmUgbW9yZSB2YXJpYWJsZSB0aGFuIHBhaXJzIG9mIGNlbGxzIGZyb20gdGhlIHNhbWUgZG9ub3IpLCByYXRoZXIgdGhhbiBzdHJpY3RseSBkZWZpbmluZyB2YXJpYW5jZSBvZiBhIGdpdmVuIGdlbmUuIFRoaXMgYW5hbHlzaXMgaXMgaW50ZW5kZWQgbW9zdGx5IGFzIGEgbGVhZC1pbiB0byBydW5uaW5nIHZhcmlhbmNlIHBhcnRpdGlvbmluZyBhbmFseXNpcyAoc2VlIG90aGVyIHNjcmlwdHMpOiBoZXJlIHdlIGZpbmQgZ2VuZXMgdGhhdCBhcmUgdmFyaWFibGUgZm9yIGFueSByZWFzb24gYW5kIHRoZXJlIHdlIHNlZSB3aGF0IGZyYWN0aW9uIG9mIGEgZ2VuZSdzIHZhcmlhbmNlIHJlbGF0ZXMgdG8gZGlmZmVyZW50IG1ldGFkYXRhIHZhcmlhYmxlcy4gIAogIApTdGFydCBieSBjYWxjdWxhdGluZyBleHByZXNzZWQgZ2VuZXMuICAKICAKYGBge3IgY2VsbCB0eXBlIGdlbmVzfQojIEZpbmQgY2VsbCB0eXBlIGdlbmVzCmtwR24gPC0gbGlzdCgpCmZvciAoY3QgaW4gc3ViY2xhc3Nlcyl7CiAgdG1wU2FtcCAgICA8LSBzdWJzYW1wbGVDZWxscyhzZXVyYXRfYWxsW1tjdF1dJGRvbm9yX25hbWUsY2VsbHNQZXJQZXJtdXRhdGlvbikgICMgU3BlZWQgdXAgcXVhbnRpbGUgY2FsY3VsYXRpb24gYW5kIG1ha2UgaXQgZmFpcmVyIGJ5IHN1YnNhbXBsaW5nIGJ5IGRvbm9yCiAga3BHbltbY3RdXSA8LSBhcHBseShzZXVyYXRfYWxsW1tjdF1dQGFzc2F5cyRSTkFAZGF0YVssdG1wU2FtcF0sMSxxdWFudGlsZSxwcm9icz0wLjc1KT4wICAjIEluY2x1ZGUgZ2VuZXMgZXhwcmVzc2VkIGluID4yNSUgb2YgY2VsbHMKfQpgYGAKICAKICAKTm93IGNhbGN1bGF0ZSB0aGUgZ2VuZSBkaWZmcyBhbmQgdmFyaWFuY2VzLiAgCiAgCmBgYHtyIGRpc3RhbmNlcyBiZXR3ZWVuIGNlbGwgcGFpcnN9CiMgVmFyaWFibGUgc2V0IHVwCmNlbGxzUGVyU3ViY2xhc3MgPC0gNzUwMCAjMjUwMCAjIEF2ZXJhZ2Ugb2YgMTAwIGNlbGwgcGFpcnMgcGVyIGRvbm9yCmdlbmVfdmFyaWFuY2VzICAgPC0gZ2VuZV92YXJpYW5jZXNfcGVybXV0ZWQgPC0gZ2VuZV9kaWZmcyA8LSBnZW5lX2RpZmZzX3Blcm11dGVkIDwtIGxpc3QoKQoKIyBDYWxjdWxhdGUgZGlmZmVyZW5jZXMgYmV0d2VlbiBwYWlycyBvZiBjZWxscyBhbmQgYXNzb2NpYXRlIHZhcmlhdGlvbiBvZiB0aGVzZSBkaWZmZXJlbmNlcwpwcmludChwYXN0ZShkYXRlKCksIi0gc3RhcnQiKSkKZm9yIChjdCBpbiBzdWJjbGFzc2VzKXsKICB0YiAgICAgICAgPC0gdGFibGUoc2V1cmF0X2FsbFtbY3RdXSRkb25vcl9uYW1lKQogIHRtcERvbm9ycyA8LSBuYW1lcyh0YilbdGI+PTRdCiAgdG1wU2FtcCAgIDwtIGlzLmVsZW1lbnQoc2V1cmF0X2FsbFtbY3RdXSRkb25vcl9uYW1lLHRtcERvbm9ycykKICBzZXQuc2VlZCh3aGljaChzdWJjbGFzc2VzPT1jdCkpCiAgZG9ub3IxICAgIDwtIHNhbXBsZSh0bXBEb25vcnMsY2VsbHNQZXJTdWJjbGFzcyozLHJlcGxhY2U9VFJVRSkKICBzZXQuc2VlZCh3aGljaChzdWJjbGFzc2VzPT1jdCkrMSkKICBkb25vcjIgICAgPC0gc2FtcGxlKHRtcERvbm9ycyxjZWxsc1BlclN1YmNsYXNzKjMscmVwbGFjZT1UUlVFKQogIGtwVG1wICAgICA8LSBkb25vcjEhPWRvbm9yMgogIGRvbm9yMSAgICA8LSBkb25vcjFba3BUbXBdWzE6Y2VsbHNQZXJTdWJjbGFzc10KICBkb25vcjIgICAgPC0gZG9ub3IyW2twVG1wXVsxOmNlbGxzUGVyU3ViY2xhc3NdCiAgY2VsbDFhIDwtIGNlbGwxYiA8LSBjZWxsMmEgPC0gTlVMTAogIGZvciAoeiBpbiAxOmNlbGxzUGVyU3ViY2xhc3MpewogICAgYzFhICAgIDwtIHNhbXBsZSh3aGljaChzZXVyYXRfYWxsW1tjdF1dJGRvbm9yX25hbWU9PWRvbm9yMVt6XSksMSkKICAgIGNlbGwxYSA8LSBjKGNlbGwxYSxjMWEpCiAgICBjZWxsMWIgPC0gYyhjZWxsMWIsc2FtcGxlKHdoaWNoKChzZXVyYXRfYWxsW1tjdF1dJGRvbm9yX25hbWU9PWRvbm9yMVt6XSkmKCgxOmxlbmd0aChzZXVyYXRfYWxsW1tjdF1dJGRvbm9yX25hbWUpKSE9YzFhKSksMSkpCiAgICBjZWxsMmEgPC0gYyhjZWxsMmEsc2FtcGxlKHdoaWNoKHNldXJhdF9hbGxbW2N0XV0kZG9ub3JfbmFtZT09ZG9ub3IyW3pdKSwxKSkKICB9CiAgCiAgIyBSZW1vdmUgZHVwbGljYXRlcwogIGZpcnN0T25lICAgPC0gbWF0Y2godW5pcXVlKGNlbGwxYSooY2VsbDFiLzcpXjIpLGNlbGwxYSooY2VsbDFiLzcpXjIpCiAgY291bnRGaXJzdCA8LSBsZW5ndGgoZmlyc3RPbmUpCiAgCiAgZGF0VG1wICAgICA8LSBzZXVyYXRfYWxsW1tjdF1dQGFzc2F5cyRSTkFAZGF0YVtrcEduW1tjdF1dLGMoY2VsbDFhW2ZpcnN0T25lXSxjZWxsMWJbZmlyc3RPbmVdLGNlbGwyYVtmaXJzdE9uZV0pXQogIGdlbmVfZGlmZnNbW2N0XV0gICAgICAgICAgPC0gYWJzKGRhdFRtcFssKDE6Y291bnRGaXJzdCldLWRhdFRtcFssKGNvdW50Rmlyc3QrMSk6KDIqY291bnRGaXJzdCldKQogIGdlbmVfZGlmZnNfcGVybXV0ZWRbW2N0XV0gPC0gYWJzKGRhdFRtcFssKDE6Y291bnRGaXJzdCldLWRhdFRtcFssKDIqY291bnRGaXJzdCsxKTooMypjb3VudEZpcnN0KV0pCiAgZ2VuZV92YXJpYW5jZXNbW2N0XV0gICAgICAgICAgPC0gdChmdmFyKHQoYXMubWF0cml4KGdlbmVfZGlmZnNbW2N0XV0pKSxyZXAoIkFsbCIsY291bnRGaXJzdCkpKQogIGdlbmVfdmFyaWFuY2VzX3Blcm11dGVkW1tjdF1dIDwtIHQoZnZhcih0KGFzLm1hdHJpeChnZW5lX2RpZmZzX3Blcm11dGVkW1tjdF1dKSkscmVwKCJBbGwiLGNvdW50Rmlyc3QpKSkKICBwcmludChwYXN0ZSgiQ2VsbCBjb3VudDoiLGN0LCI9IixkaW0oZ2VuZV9kaWZmc1tbY3RdXSlbMl0pKQp9CnByaW50KHBhc3RlKGRhdGUoKSwiLSBlbmQiKSkKYGBgCiAgCiAgClN1bW1hcml6ZSB0aGVzZSB2YWx1ZXMgYmV0d2VlbiBtYXRjaGVkIGFuZCBkaXN0aW5jdCBkb25vcnMuICAKICAKYGBge3Igc3VtbWFyaXplIHN0YXRzfQojIEZ1bmN0aW9uIGZvciBkb2luZyB0aGlzIGlzIGluIHRoZSBleHRyYSBmdW5jdGlvbiBmaWxlCnZhcmlhbmNlX3N0YXRzIDwtIGxpc3QoKQpmb3IgKGN0IGluIHN1YmNsYXNzZXMpewogIG91dCAgPC0gdChhcHBseShjYmluZChnZW5lX2RpZmZzX3Blcm11dGVkW1tjdF1dLGdlbmVfZGlmZnNbW2N0XV0pLDEsZ2V0U3RhdHMpKQogIG91dCAgPC0gY2JpbmQob3V0LHAuYWRqdXN0KG91dFssNl0sIkJIIikpICMgRGVmaW5lIEZEUiBjb3JyZWN0ZWQgcC12YWx1ZXMgICMgcXZhbHVlKG91dFssNl0pJHF2YWx1ZSkjCiAgY29sbmFtZXMob3V0KSA8LSBjKCJwZXJtdXRlZF92YXIiLCJhY3R1YWxfdmFyIiwicGVybXV0ZWRfdmFyX3NkIiwKCSAgICAgICAgICAgICAgICAgICAiYWN0dWFsX3Zhcl9zZCIsIlRzdGF0IiwiUHZhbHVlIiwiRkRSX1B2YWx1ZSIpCiAgdmFyaWFuY2Vfc3RhdHNbW2N0XV0gPC0gYXMuZGF0YS5mcmFtZShvdXQpCn0KcHJpbnQoIlN1bW1hcml6YXRpb24gY29tcGxldGUuIikKYGBgCgogIApOb3cgbGV0J3MgY2FsY3VsYXRlIGFuZCBvdXRwdXQgdGhlIG1lZGlhbiB2YXJpYXRpb24gc2NvcmVzIGZvciBlYWNoIGdlbmUuICBXaGljaCBvbmVzIGhhdmUgdGhlIGxvd2VzdCByYXRpbyAoZS5nLiwgcmF0aW8gd2l0aGluIGRvbm9yIHRlbmRzIHRvIGJlIGRyYW1hdGljYWxseSBsb3dlciB0aGFuIHdoZW4gcmFuZG9tIGNlbGxzIGFyZSB0YWtlcyBiZXR3ZWVuIGRvbm9ycykuICBUaGlzIG91dHB1dCB3aWxsIGJlIHVzZWQgZm9yICAgCiAgCmBgYHtyIGNhbGN1bGF0ZSB0aGUgbWVkaWFuIHZhcmlhdGlvbiBzY29yZXN9CmFwX3N1bW1hcmllcyA8LSBtYXRyaXgoTkEsbnJvdz1kaW0oZGF0QWxsW1sxXV0pWzFdLG5jb2w9bGVuZ3RoKHN1YmNsYXNzZXMpKQpyb3duYW1lcyhhcF9zdW1tYXJpZXMpIDwtIHJvd25hbWVzKGRhdEFsbFtbMV1dKQpjb2xuYW1lcyhhcF9zdW1tYXJpZXMpIDwtIHN1YmNsYXNzZXMKdmFyX3JlYWwgPC0gdmFyX3Blcm0gPC0gYXBfc3VtbWFyaWVzCmZvciAoY3QgaW4gc3ViY2xhc3Nlcyl7CiAgYXBfdmFyIDwtIHZhcmlhbmNlX3N0YXRzW1tjdF1dJGFjdHVhbF92YXIvdmFyaWFuY2Vfc3RhdHNbW2N0XV0kcGVybXV0ZWRfdmFyCiAgYXBfc3VtbWFyaWVzW3Jvd25hbWVzKHZhcmlhbmNlX3N0YXRzW1tjdF1dKSxjdF0gPC0gYXBfdmFyCiAgdmFyX3JlYWxbcm93bmFtZXModmFyaWFuY2Vfc3RhdHNbW2N0XV0pLGN0XSAgICAgPC0gdmFyaWFuY2Vfc3RhdHNbW2N0XV0kYWN0dWFsX3ZhcgogIHZhcl9wZXJtW3Jvd25hbWVzKHZhcmlhbmNlX3N0YXRzW1tjdF1dKSxjdF0gICAgIDwtIHZhcmlhbmNlX3N0YXRzW1tjdF1dJHBlcm11dGVkX3Zhcgp9Cm1lZGlhbl9hcCAgIDwtIGFwcGx5KGFwX3N1bW1hcmllcywxLGZ1bmN0aW9uKHgpIG1lZGlhbih4LG5hLnJtPVRSVUUpKQptZWRpYW5fcmVhbCA8LSBhcHBseSh2YXJfcmVhbCwxLG1lZGlhbixuYS5ybT1UUlVFKQptZWRpYW5fcGVybSA8LSBhcHBseSh2YXJfcGVybSwxLG1lZGlhbixuYS5ybT1UUlVFKQoKIyBXcml0ZSB0byBhIGZpbGUKb3V0IDwtIGRhdGEuZnJhbWUoZ2VuZT1uYW1lcyhtZWRpYW5fYXApLCB2YXJpYXRpb25fc2NvcmU9bWVkaWFuX2FwLCAKICAgICAgICAgICAgICAgICAgYWN0dWFsX3ZhcmlhdGlvbj1tZWRpYW5fcmVhbCwgcGVybXV0ZWRfdmFyaWF0aW9uPW1lZGlhbl9wZXJtKQpvdXQgPC0gb3V0WyFpcy5uYShvdXQkdmFyaWF0aW9uX3Njb3JlKSxdCndyaXRlLmNzdihvdXQsICJ2YXJpYXRpb25fc2NvcmUuY3N2Iiwgcm93Lm5hbWVzPUZBTFNFKQpgYGAKICAKICAKTm93IGRpcmVjdGx5IGNvbXBhcmUgZGlzdGFuY2VzIGJldHdlZW4gY2VsbHMgZnJvbSBkaWZmZXJlbnQgdnMuIHRoZSBzYW1lIGRvbm9ycy4gIAogIApgYGB7ciBwZXJtdXRlZCB2cyBhY3R1YWwgdmFyaWF0aW9uLCBmaWcuaGVpZ2h0PTYsIGZpZy53aWR0aD02fQpwbHQgPC0gYXMuZ2dwbG90KGZ1bmN0aW9uKCl7CnZlcmJvc2VTY2F0dGVycGxvdChtZWRpYW5fcGVybSxtZWRpYW5fcmVhbCx4bGFiPSJHZW5lIGRpc3RhbmNlIGluIGNlbGxzIGZyb20gZGlmZmVyZW50IGRvbm9yIiwKICAgICAgICAgICAgICAgICAgIHlsYWI9IkdlbmUgZGlzdGFuY2UgaW4gY2VsbHMgZnJvbSBzYW1lIGRvbm9yIixwY2g9MTksY2V4PTAuNSkKc2VnbWVudHMoMC41LDAuNSw1LDUsbHdkPTIsY29sPSJncmV5IikKdGV4dCgyLDQscGFzdGUwKHNpZ25pZigxMDAqbWVhbihtZWRpYW5fcGVybTxtZWRpYW5fcmVhbCxuYS5ybT1UUlVFKSwyKSwiJSBTPkQiKSxjZXg9MS41KQpnbiA8LSBuYW1lcyhoZWFkKHNvcnQobWVkaWFuX2FwKSwxNSkpCnRleHQobWVkaWFuX3Blcm1bZ25dLG1lZGlhbl9yZWFsW2duXSxnbixjZXg9MC43KQp9KQpwbHQKZ2dzYXZlKCJnZW5lVmFyaWFuY2VfcmVhbFZzUGVybXV0ZWRfc2NhdHRlcnBsb3QucGRmIiwgcGxvdD1wbHQsIGhlaWdodCA9IDYsIHdpZHRoID0gNikKYGBgCgogIApXaGljaCBnZW5lcyBwZXIgc3ViY2xhc3MgYXJlIHNpZ25pZmljYW50IGluIGVhY2ggZGlyZWN0aW9uIGFmdGVyIEZEUiBjb3JyZWN0aW9uPyAgV2UgYWxzbyBnZXQgdGhlIGNvdW50LgogIApgYGB7ciB3aGljaCBhcmUgc2lnbmlmaWNhbnQgZ2VuZXN9CiMjIER5bmFtaWMgc2VsZWN0aW9uIG9mIHRocmVzaG9sZCB0byBlbnN1cmUgbm8gcmFuZG9tIGdlbmVzCnRocmVzaCA9IDEKZm9yIChjdCBpbiBzdWJjbGFzc2VzKSAKICB0aHJlc2g9bWluKGModGhyZXNoLHZhcmlhbmNlX3N0YXRzW1tjdF1dJEZEUl9QdmFsdWVbdmFyaWFuY2Vfc3RhdHNbW2N0XV0kVHN0YXQ8MF0pKQpwcmludChwYXN0ZSgiRHluYW1pYyB0aHJlc2hvbGQ6IixzaWduaWYodGhyZXNoLDMpKSkKdmFyR2VuZXMgPC0gTlVMTAoKI3RocmVzaD0wLjAwMQoKZm9yIChjdCBpbiBzdWJjbGFzc2VzKXsKICB0bXAgIDwtIGFicygxMCoodmFyaWFuY2Vfc3RhdHNbW2N0XV0kVHN0YXQ8MCkrKHZhcmlhbmNlX3N0YXRzW1tjdF1dJEZEUl9QdmFsdWU8dGhyZXNoKS01KS0zCiAgb3V0ICA8LSBkYXRhLmZyYW1lKGdlbmU9cm93bmFtZXModmFyaWFuY2Vfc3RhdHNbW2N0XV0pLGRpcmVjdGlvbj1jKCJoaWdoIiwiTlMiLCJsb3ciKVt0bXBdLHN1YmNsYXNzPWN0KQogIHZhckdlbmVzIDwtIHJiaW5kKHZhckdlbmVzLG91dCkKfQoKIyMgT3V0cHV0IHRoZSBnZW5lcyB0byBhIHRhYmxlCndyaXRlLmNzdih2YXJHZW5lcywidmFyaWFibGVfZ2VuZXMuY3N2Iixyb3cubmFtZXMgPSBGQUxTRSkKYGBgCiAgCiAgClBsb3QgdGhlIG51bWJlci4gIAogIApgYGB7ciBzaWduaWZpY2FudCBnZW5lIGNvdW50LCBmaWcuaGVpZ2h0PTgsIGZpZy53aWR0aD05fQpoaWdoVmFyIDwtIHRhYmxlKGZhY3Rvcih2YXJHZW5lcyRzdWJjbGFzc1t2YXJHZW5lcyRkaXJlY3Rpb249PSJoaWdoIl0sbGV2ZWxzPXN1YmNsYXNzZXMpKQpsb3dWYXIgIDwtIHRhYmxlKGZhY3Rvcih2YXJHZW5lcyRzdWJjbGFzc1t2YXJHZW5lcyRkaXJlY3Rpb249PSJsb3ciXSxsZXZlbHM9c3ViY2xhc3NlcykpCgpwbHQgPC0gYXMuZ2dwbG90KGZ1bmN0aW9uKCl7CnBhcihtZnJvdz1jKDIsMSkpCnBhcihtYXI9Yyg1LDUsMywzKSkKYmFycGxvdChoaWdoVmFyLGxhcz0yLHhsYWI9IiIsbWFpbj1wYXN0ZSgiSGlnaCB2YXJpYW5jZSBnZW5lcyAtIFAoQkguYWRqKSA8IixzaWduaWYodGhyZXNoLDIpKSxjb2w9Y29sc1tuYW1lcyhoaWdoVmFyKV0seWxhYj0iQ291bnQiKQpiYXJwbG90KGxvd1ZhcixsYXM9Mix4bGFiPSIiLG1haW49cGFzdGUoIkxvdyB2YXJpYW5jZSBnZW5lcyAtIFAoQkguYWRqKSA8IixzaWduaWYodGhyZXNoLDIpKSxjb2w9Y29sc1tuYW1lcyhoaWdoVmFyKV0seWxhYj0iQ291bnQiKQp9KQpwbHQKZ2dzYXZlKCJ2YXJpYW5jZV9nZW5lX2NvdW50c19iYXJwbG90LnBkZiIsIHBsb3Q9cGx0LCBoZWlnaHQgPSA4LCB3aWR0aCA9IDkpCmBgYAogIAogIApUaGVyZSBhcmUgYSBsb3Qgb2YgZ2VuZXMgd2l0aCBzaWduaWZpY2FudGx5IGhpZ2hlciB0aGFuIGNoYW5jZSB2YXJpYXRpb24gaW4gbWFueSBjZWxsIHR5cGVzLCB3aXRoIGVzc2VudGlhbGx5IG5vIGdlbmVzIGluIGFueSBjZWxsIHR5cGUgc2hvd2luZyBsZXNzIHZhcmlhdGlvbiB0aGFuIGV4cGVjdGVkIGJ5IGNoYW5jZSwgYXMgZXhwZWN0ZWQuICBNYW55IG1vcmUgaGlnaCB2YXJpYW5jZSBnZW5lcyBpbiBnbHV0YW1hdGVyZ2ljIHRoYW4gR0FCQWVyZ2ljIHR5cGVzLCBjb25zaXN0ZW50IHdpdGggb3RoZXIgbWV0cmljcy4gIAogIApXaGF0IGlmIHdlIHNjYWxlIGJ5IHRoZSBudW1iZXIgb2YgZXhwcmVzc2VkIGdlbmVzLCBzaW5jZSBub24tbmV1cm9uYWwgdHlwZXMgZ2VuZXJhbGx5IGhhdmUgZmV3ZXIgZ2VuZXMgZXhwcmVzc2VkPwogIApgYGB7ciBzY2FsZWQgZ2VuZSBjb3VudCwgZmlnLmhlaWdodD04LCBmaWcud2lkdGg9OX0KZ2VuZUNvdW50UGVyU3ViY2xhc3MgPC0gdGFibGUodmFyR2VuZXMkc3ViY2xhc3MpW25hbWVzKGhpZ2hWYXIpXQoKcGx0IDwtIGFzLmdncGxvdChmdW5jdGlvbigpewpwYXIobWZyb3c9YygyLDEpKQpwYXIobWFyPWMoNSw1LDMsMykpCmJhcnBsb3QoaGlnaFZhci9nZW5lQ291bnRQZXJTdWJjbGFzcyxsYXM9Mix4bGFiPSIiLCBjb2w9Y29sc1tuYW1lcyhoaWdoVmFyKV0sbWFpbj0iRnJhY3Rpb24gb2YgZXhwcmVzc2VkIGdlbmVzIHRoYXQgaGF2ZSBoaWdoIHZhcmlhbmNlIikKYWJsaW5lKGg9YyguMSwuMiwuMyksbHR5PSJkYXNoZWQiLGNvbD0iZ3JleSIpCn0pCnBsdApnZ3NhdmUoInZhcmlhbmNlX2dlbmVfZnJhY3Rpb25fYmFycGxvdC5wZGYiLCBwbG90PXBsdCwgaGVpZ2h0ID0gOCwgd2lkdGggPSA5KQpgYGAKCiAgCkxldCdzIHNlZSB3aGV0aGVyIHRoZXNlIGdlbmUgY291bnRzIGFyZSBkZXBlbmRlbnQgb24gdGhlIG51bWJlciBvZiBjZWxscyBwZXIgc3ViY2xhc3MgcGVyIGRvbm9yLiAgCiAgCmBgYHtyIGNvbXBhcmUgdG8gY2VsbCByYXJpdHksIGZpZy5oZWlnaHQ9NiwgZmlnLndpZHRoPTEyfQpwbHQgPC0gYXMuZ2dwbG90KGZ1bmN0aW9uKCl7CnBhcihtZnJvdz1jKDEsMikpCnZlcmJvc2VTY2F0dGVycGxvdChudW1iZXJPZkNlbGxzLGhpZ2hWYXJbc3ViY2xhc3Nlc10seWxhYj0iTnVtYmVyIG9mIGhpZ2ggdmFyaWFuY2UgZ2VuZXMiLAogICAgICAgICAgICAgICAgICAgeGxhYj0iTnVtYmVyIG9mIHRvdGFsIGNlbGxzIixtYWluPSIiLGNvbD1zdWJjbGFzc19jb2xvcnNbbmFtZXMobnVtYmVyT2ZDZWxscyldLHBjaD0xOSx4bGltPWMoLTEwMDAwLDEwMDAwMCkpCnRleHQobnVtYmVyT2ZDZWxscyxoaWdoVmFyW3N1YmNsYXNzZXNdLHN1YmNsYXNzZXMsY2V4PTAuNjUpCnZlcmJvc2VTY2F0dGVycGxvdChyb3dNZWFucyhjb3JyZWN0X3ZhciksaGlnaFZhcltzdWJjbGFzc2VzXSx5bGFiPSJOdW1iZXIgb2YgaGlnaCB2YXJpYW5jZSBnZW5lcyIsCiAgICAgICAgICAgICAgICAgICB4bGFiPSJNZWFuIFJGIHByZWRpY3Rpb24iLG1haW49IiIsY29sPXN1YmNsYXNzX2NvbG9yc1tuYW1lcyhudW1iZXJPZkNlbGxzKV0scGNoPTE5LHhsaW09YygwLjMsMC44KSkKdGV4dChyb3dNZWFucyhjb3JyZWN0X3ZhciksaGlnaFZhcltzdWJjbGFzc2VzXSxzdWJjbGFzc2VzLGNleD0wLjY1KQp9KQpwbHQKZ2dzYXZlKCJSRl9wcmVkaWN0aW9uX3Nhbml0eUNoZWNrX3NjYXR0ZXJwbG90c19yZXZpc2l0LnBkZiIsIHBsb3Q9cGx0LCBoZWlnaHQgPSA2LCB3aWR0aCA9IDEyKQoKIyBSZXBlYXQsIGV4Y2x1ZGluZyBjZWxsIHR5cGVzIHdpdGggPDEwMDAgY2VsbHMgKHdlIG1heSB3YW50IHRvIGRvIHRoaXMgYW55d2F5KQpwbHQgPC0gYXMuZ2dwbG90KGZ1bmN0aW9uKCl7CnBhcihtZnJvdz1jKDEsMikpCmtwPW51bWJlck9mQ2VsbHM+MTAwMAp2ZXJib3NlU2NhdHRlcnBsb3QobnVtYmVyT2ZDZWxsc1trcF0saGlnaFZhcltzdWJjbGFzc2VzXVtrcF0seWxhYj0iTnVtYmVyIG9mIGhpZ2ggdmFyaWFuY2UgZ2VuZXMiLAogICAgICAgICAgICAgICAgICAgeGxhYj0iTnVtYmVyIG9mIHRvdGFsIGNlbGxzIixtYWluPSI8MTAwMGNlbGxzIixjb2w9c3ViY2xhc3NfY29sb3JzW25hbWVzKG51bWJlck9mQ2VsbHNba3BdKV0scGNoPTE5LHhsaW09YygtMTAwMDAsMTAwMDAwKSkKdGV4dChudW1iZXJPZkNlbGxzW2twXSxoaWdoVmFyW3N1YmNsYXNzZXNdW2twXSxzdWJjbGFzc2VzW2twXSxjZXg9MC42NSkKdmVyYm9zZVNjYXR0ZXJwbG90KHJvd01lYW5zKGNvcnJlY3RfdmFyKVtrcF0saGlnaFZhcltzdWJjbGFzc2VzXVtrcF0seWxhYj0iTnVtYmVyIG9mIGhpZ2ggdmFyaWFuY2UgZ2VuZXMiLAogICAgICAgICAgICAgICAgICAgeGxhYj0iTWVhbiBSRiBwcmVkaWN0aW9uIixtYWluPSI8MTAwMGNlbGxzIixjb2w9c3ViY2xhc3NfY29sb3JzW25hbWVzKG51bWJlck9mQ2VsbHNba3BdKV0scGNoPTE5LHhsaW09YygwLjMsMC44KSkKdGV4dChyb3dNZWFucyhjb3JyZWN0X3Zhcilba3BdLGhpZ2hWYXJbc3ViY2xhc3Nlc11ba3BdLHN1YmNsYXNzZXNba3BdLGNleD0wLjY1KQp9KQpwbHQKZ2dzYXZlKCJSRl9wcmVkaWN0aW9uX3Nhbml0eUNoZWNrX3NjYXR0ZXJwbG90c19yZXZpc2l0QW5kU3Vic2V0LnBkZiIsIHBsb3Q9cGx0LCBoZWlnaHQgPSA2LCB3aWR0aCA9IDEyKQpgYGAKICAKRXhjZXB0IGZvciByZWFsbHkgcmFyZSBjZWxsIHR5cGVzLCB0aGVyZSBudW1iZXIgb2YgaGlnaCB2YXJpYW5jZSBnZW5lcyAoMSkgaXNuJ3QgcmVsYXRlZCB0byB0b3RhbCBudW1iZXIgb2YgY2VsbHMgYnV0ICgyKSBpcyByZWxhdGVkIHRvIHRoZSBtZWFuIFJGIHByZWRpY3Rpb24uICBXZSBtYXkgd2FudCB0byBjb25zaWRlciByZW1vdmluZyB0aGVzZSBmb3VyIHR5cGVzIGZvciB0aGUgZW50aXJlIGFuYWx5c2lzLiAgCiAgClJ1biBnZW5lIG9udG9sb2d5IGVucmljaG1lbnQgYW5hbHlzaXMgKHVzaW5nIGZnc2VhKSBmb3IgZWFjaCBzdWJjbGFzcywgYW5kIHRoZW4gbG9vayBhdCB0aGUgcmVzdWx0cy4gIAogIApgYGB7ciBmZ3NlYSAzLCB3YXJuaW5nPUZBTFNFfQpsb2FkKHBhc3RlMChpbnB1dEZvbGRlciwiR090ZXJtcy5SRGF0YSIpKSAgIyBTaG91bGQgaGF2ZSBiZWVuIGdlbmVyYXRlZCBwcmV2aW91c2x5CkdPdGVybXNfYWxsIDwtIGMoR090ZXJtc1tbMV1dLEdPdGVybXNbWzJdXSxHT3Rlcm1zW1szXV0pCmFsbCAgICAgICAgIDwtIHVuaXF1ZSh2YXJHZW5lcyRnZW5lKQpnb0hpZ2ggICAgICA8LSBsaXN0KCkKdG9wSGl0c0ggICAgPC0gTlVMTApmb3IgKGN0IGluIHN1YmNsYXNzZXMpewogIGduSCA8LSB2YXJHZW5lcyRnZW5lWyh2YXJHZW5lcyRzdWJjbGFzcz09Y3QpJih2YXJHZW5lcyRkaXJlY3Rpb249PSJoaWdoIildCiAgZ29IaWdoW1tjdF1dIDwtIGZvcmEoR090ZXJtc19hbGwsIGdlbmVzPWduSCwgdW5pdmVyc2U9YWxsLCBtaW5TaXplID0gMTAsIG1heFNpemUgPSA1MDApCiAgdG9wSGl0c0ggPC0gcmJpbmQodG9wSGl0c0gsZGF0YS5mcmFtZShnb0hpZ2hbW2N0XV1bLGMoMSwzKV0sdHlwZT1jdCxkaXJlY3Rpb249IkhpZ2ggdmFyaWFuY2UiKVsxOjEwLF0pCn0KdG9wSGl0c0hbLDFdIDwtIHN1YnN0cih0b3BIaXRzSFssMV0sMSw0MCkgIyBGb3IgbGVnaWJpbGl0eQp0b3BIaXRzSApgYGAKICAKRm9yIG5vbi1uZXVyb25hbCBjZWxscywgdGhlIG1vc3QgdmFyaWFibGUgZ2VuZXMgYXJlIGludGVyZXN0aW5nbHkgc3luYXB0aWMgZ2VuZXMsIGZvciBtaWNyb2dsaWEsIHRoZXkgYXJlIGluZmxhbW1hdG9yeSByZXNwb25zZSBnZW5lcyAobGlrZWx5IHJlZmxlY3RpbmcgbWljcm9nbGlhbCBzdGF0ZXMpLCBidXQgZm9yIG5ldXJvbmFsIHR5cGVzIHRoZXJlIGFyZSBubyBzaWduaWZpY2FudCBjYXRlZ29yaWVzIGZvciBuZWFybHkgYW55IHN1YmNsYXNzLiAgCiAgClBsb3Qgc29tZSBleGFtcGxlIGdlbmVzIGFzIGEgc2FuaXR5IGNoZWNrLCB3aXRoIGVhY2ggcG9pbnQgY29ycmVzcG9uZGluZyB0byB0aGUgYXZlcmFnZSB2YWx1ZSBwZXIgY2VsbCB0eXBlLiAgCiAgCmBgYHtyIGV4YW1wbGUgZ2VuZSBwbG90cywgZmlnLmhlaWdodD00LjUsIGZpZy53aWR0aD0xNSwgd2FybmluZz1GQUxTRX0KY3QgPC0gIkw2IENUIgpwbHQgPC0gYXMuZ2dwbG90KGZ1bmN0aW9uKCl7CnBhcihtZnJvdz1jKDEsNCkpCmZvciAoZ24gaW4gYygiWElTVCIsIkxSUkMzN0EiLCJKVU5EIiwiTUFMQVQxIikpewogIHZhcnggPC0gYyhnZW5lX2RpZmZzX3Blcm11dGVkW1tjdF1dW2duLF0sZ2VuZV9kaWZmc1tbY3RdXVtnbixdKQogIGdwICAgPC0gYyhyZXAoIkRpZmZlcmVudCIsbGVuZ3RoKGdlbmVfZGlmZnNfcGVybXV0ZWRbW2N0XV1bZ24sXSkpLHJlcCgiU2FtZSBkb25vcnMiLGxlbmd0aChnZW5lX2RpZmZzW1tjdF1dW2duLF0pKSkKICBncCAgIDwtIGdwWyFpcy5uYSh2YXJ4KV0KICB2YXJ4ICAgPC0gdmFyeFshaXMubmEodmFyeCldCiAgdmVyYm9zZUJveHBsb3QodmFyeCxncCxtYWluPWduLHB0LmNleD0xLjIsIGFkZFNjYXR0ZXJwbG90ID0gVFJVRSwgY2V4LmxhYj0xLjUsIHBjaD0xOSwKICAgICAgICAgICAgICAgICB4bGFiPSIiLHlsYWI9ImFicyhsb2cyQ1BNKE4xKS1sb2cyQ1BNKE4yKSkiLGNvbD0id2hpdGUiLHB0LmNvbCA9ICJibGFjayIpCn0KfSkKcGx0Cmdnc2F2ZSgiZXhhbXBsZUhpZ2hWYXJpYW5jZUdlbmVzX3NjYXR0ZXJwbG90cy5wZGYiLCBwbG90PXBsdCwgaGVpZ2h0ID0gNC41LCB3aWR0aCA9IDE1KQpgYGAKICAKICAKTmV4dCwgbGV0J3Mgc2VlIGhvdyBmcmVxdWVudGx5IHVuaXF1ZSBnZW5lcyBhcHBlYXIgaW4gbXVsdGlwbGUgY2VsbCB0eXBlcyBhcyBoaWdobHkgdmFyaWFibGUuICAKICAKYGBge3IgaGlnaCB2YXJpYWJsZSBnZW5lIGNvdW50LCBmaWcuaGVpZ2h0PTQsZmlnLndpZHRoPTd9CnZhckNvdW50IDwtIHRhYmxlKHZhckdlbmVzJGdlbmVbdmFyR2VuZXMkZGlyZWN0aW9uPT0iaGlnaCJdKQpwbHQgPC0gYXMuZ2dwbG90KGZ1bmN0aW9uKCl7CmJhcnBsb3QodGFibGUodmFyQ291bnQpLGxvZz0ieSIsbGFzPTIseWxhYj0iIyBnZW5lcyIsY29sPSJibGFjayIsCiAgICAgICAgeGxhYj0iIyBvZiBvZiBzdWJjbGFzc2VzIGZvciB3aGljaCBnZW5lIGlzIGhpZ2hseSB2YXJpYWJsZSIpCn0pCnBsdApnZ3NhdmUoIm51bWJlcl9vZl9zdWJjbGFzc2VzX3Blcl9oaWdoX3ZhcmlhbmNlX2dlbmUucGRmIiwgcGxvdD1wbHQsIGhlaWdodCA9IDQsIHdpZHRoID0gNykKZGF0YS5mcmFtZSgtc29ydCgtdmFyQ291bnRbdmFyQ291bnQ+PTE1XSkpCmRhdGEuZnJhbWUodmFyQ291bnRbc29ydChuYW1lcyhoZWFkKHNvcnQobWVkaWFuX2FwKSwxNSkpKV0pCmBgYAogIAogIApGaW5hbGx5LCBwbG90IHNvbWUgY29tcGFyaXNvbnMgYmV0d2VlbiBtZWRpYW5fYXAgYW5kIHZhcmlvdXMgcmFuZG9tIGZvcmVzdCBtZXRyaWNzLiAgCiAgCmBgYHtyIFJGIGdlbmUgbWFsZSBmZW1hbGUgcGxvdHMsIGZpZy5oZWlnaHQ9NSxmaWcud2lkdGg9MTF9CiMgUGxvdCBzb21lIGdlbmUgY29tcGFyaXNvbnMKcGx0IDwtIGFzLmdncGxvdChmdW5jdGlvbigpewpwYXIobWZyb3c9YygxLDIpKQpnZyA8LSBpbnRlcnNlY3Qocm93bmFtZXMoZ2VuZV9pbXBvcnRhbmNlc19tZWFuKSxuYW1lcyhtZWRpYW5fYXApWyFpcy5uYShtZWRpYW5fYXApXSkKYT0gbWVkaWFuX2FwW2dnXQpiPSByb3dNZWFucyhnZW5lX2ltcG9ydGFuY2VzX21lYW4sbmEucm09VFJVRSkKbmFtZXMoYikgPC0gcm93bmFtZXMoZ2VuZV9pbXBvcnRhbmNlc19tZWFuKQpiID0gYltnZ10KdmVyYm9zZVNjYXR0ZXJwbG90KGEsYixwY2g9MTksY2V4PTAuNSx4bGFiPSJNZWFuIGdlbmUgdmFyaWFuY2UgcmF0aW8iLCB5bGFiPSJNZWRpYW4gZGVjcmVhc2UgUkYgYWNjdXJhY3kiKQphYmxpbmUoMCwxKQprcCA8LSBiPj0wLjAwMQp0ZXh0KGFba3BdLGJba3BdLG5hbWVzKGEpW2twXSxjZXg9MC41KQoKYSA9IHJvd01lYW5zKG1hbGVfaW1wb3J0YW5jZXNfbWVhbixuYS5ybT1UUlVFKQpiID0gcm93TWVhbnMoZmVtYWxlX2ltcG9ydGFuY2VzX21lYW4sbmEucm09VFJVRSkKbmFtZXMoYSkgPC0gbmFtZXMoYikgPC0gcm93bmFtZXMobWFsZV9pbXBvcnRhbmNlc19tZWFuKQp2ZXJib3NlU2NhdHRlcnBsb3QoYSxiLHBjaD0xOSxjZXg9MC41LHhsYWI9Ik1lYW4gUkYgc2NvcmUgaW4gTWFsZSIseWxhYj0iTWVhbiBSRiBzY29yZSBpbiBGZW1hbGUiKQphYmxpbmUoMCwxKQprcCA8LSBhPj0wLjAwMQp0ZXh0KGFba3BdLGJba3BdLG5hbWVzKGEpW2twXSxjZXg9MC41KQp9KQpwbHQKZ2dzYXZlKCJSRl9nZW5lX2ltcG9ydGFuY2VfdnNfdmFyaWFibGVfZ2VuZXMucGRmIiwgcGxvdD1wbHQsIGhlaWdodCA9IDUsIHdpZHRoID0gMTEpCmBgYAogIApTZXggY2hyb21vc29tZSBnZW5lcyAoZXZlbiB5LWNocm9tb3NvbWUgZ2VuZXMhKSBoYXZlIGhpZ2hlciBpbXBvcmFudGFuY2UgZm9yIGZlbWFsZSB0aGFuIG1hbGUgZG9ub3JzLiAgSSB3b25kZXIgaWYgdGhpcyBpcyBiZWNhdXNlIHRoZXJlIGFyZSBmZXdlciBmZW1hbGVzIG92ZXJhbGwgaW4gdGhlIHN0dWR5PyAgCiAgCiAgCiMjIyBTYXZlIHJlcXVpcmUgZmlsZXMKICAKVGhpcyBzZWN0aW9uIHNhdmVzIGFsbCB0aGUgdmFyaWFibGVzIG5lZWRlZCBmb3IgdXNlIHdpdGggdGhlIFNFQS1BRCBzY3JpcHQuICBUaGlzIHN0ZXAgY2FuIGJlIHNraXBwZWQgaWYgeW91J2QgcHJlZmVyIHRvIGNvbnRpbnVlIHRoZSBuZXh0IHNjcmlwdCB3aXRob3V0IGNsb3NpbmcgeW91ciB3b3JraW5nIHNjcmlwdCAoeW91IGNvdWxkIGFsc28ganVzdCBzYXZlIGV2ZXJ5dGhpbmcpLiAgCiAgCmBgYHtyIHNhdmUgc3R1ZmYgZm9yIFNFQUFELCBldmFsPUZBTFNFfQpzYXZlLmltYWdlKCJpbnB1dF9mb3Jfc2VhYWRfYW5hbHlzaXMuUkRhdGEiKQpgYGAKCiAgCk91dHB1dCBzZXNzaW9uIGluZm9ybWF0aW9uLiAgCiAgCmBgYHtyIHNlc3Npb25JbmZvfQpzZXNzaW9uSW5mbygpCmBgYAogIAogIAoKIAogIA==